Compare commits
745 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
98f50811f9 | ||
![]() |
ee03415894 | ||
![]() |
8943341605 | ||
![]() |
836e8f8ce9 | ||
![]() |
e891dbe6b9 | ||
![]() |
c2917d46d5 | ||
![]() |
96086bcb0c | ||
![]() |
6e4522c15c | ||
![]() |
deb1f4f702 | ||
![]() |
7a6c7123bd | ||
![]() |
abab18e165 | ||
![]() |
762d975a87 | ||
![]() |
405872aaf0 | ||
![]() |
0a33cc3b8a | ||
![]() |
946b7bc25a | ||
![]() |
18990df41f | ||
![]() |
3129ddab47 | ||
![]() |
f4165c9812 | ||
![]() |
6848915883 | ||
![]() |
10bae9577c | ||
![]() |
7c4257a222 | ||
![]() |
8e579efe47 | ||
![]() |
b44bbc81a0 | ||
![]() |
aac61c69c2 | ||
![]() |
6fb491471b | ||
![]() |
40294a7c9e | ||
![]() |
c6fb4e238d | ||
![]() |
10c45b185c | ||
![]() |
2ba73f7ff9 | ||
![]() |
d79366d8b2 | ||
![]() |
dd5b817f0f | ||
![]() |
977e1dcb67 | ||
![]() |
a31301cb4f | ||
![]() |
7da96a1175 | ||
![]() |
3f68a27a22 | ||
![]() |
cb2e7df96a | ||
![]() |
6cbdd00169 | ||
![]() |
289d10d30e | ||
![]() |
e5e6c4e1d7 | ||
![]() |
894a01a5a5 | ||
![]() |
64b1630d02 | ||
![]() |
372bd22475 | ||
![]() |
d545ee3f0b | ||
![]() |
a194c90d6f | ||
![]() |
90be664981 | ||
![]() |
85f07fcd89 | ||
![]() |
3a02d03cb1 | ||
![]() |
8b2c7291d3 | ||
![]() |
3de04156e9 | ||
![]() |
ee731a908d | ||
![]() |
70920a64be | ||
![]() |
c17cda3e95 | ||
![]() |
f3220470e8 | ||
![]() |
b3b79a652d | ||
![]() |
ab558ce172 | ||
![]() |
9d23271c14 | ||
![]() |
1651289485 | ||
![]() |
6f4d328cde | ||
![]() |
88eae92d9b | ||
![]() |
49e48a5a8c | ||
![]() |
d0199a326f | ||
![]() |
19f40745f8 | ||
![]() |
d0ffc9b6ce | ||
![]() |
88f9424df7 | ||
![]() |
59d8d1eb62 | ||
![]() |
af9a914ecc | ||
![]() |
c3ac051bbb | ||
![]() |
2e9654725e | ||
![]() |
20e8b83421 | ||
![]() |
2ff840aba6 | ||
![]() |
44d6e1124c | ||
![]() |
ef93161840 | ||
![]() |
73ef17b96d | ||
![]() |
d6e08e90a8 | ||
![]() |
2b7e4d6b4e | ||
![]() |
15da4e17bd | ||
![]() |
68547dce4c | ||
![]() |
238b03a8c7 | ||
![]() |
83e635de0e | ||
![]() |
3cb4443128 | ||
![]() |
80a26b440d | ||
![]() |
a2243190ff | ||
![]() |
7da5fa0e5c | ||
![]() |
8663dbcb97 | ||
![]() |
12a42b6c47 | ||
![]() |
335816cbf7 | ||
![]() |
6231f82248 | ||
![]() |
fc16bed7ca | ||
![]() |
87d93b33b8 | ||
![]() |
12bda1d4f5 | ||
![]() |
2c3257d4ea | ||
![]() |
1e8e82ec0e | ||
![]() |
58e63f387f | ||
![]() |
5beea25b31 | ||
![]() |
58f87a6aa7 | ||
![]() |
96ead07606 | ||
![]() |
b8b3f8910e | ||
![]() |
2a922214a2 | ||
![]() |
3bd2bea54d | ||
![]() |
42e6dfa40d | ||
![]() |
64fb78ac5e | ||
![]() |
9e36efbd21 | ||
![]() |
4d08484a12 | ||
![]() |
61a6ab9bbd | ||
![]() |
709c6dedf2 | ||
![]() |
abc9038580 | ||
![]() |
9dba8cd494 | ||
![]() |
7f1aee6dc8 | ||
![]() |
858a85a4dc | ||
![]() |
c58db4d005 | ||
![]() |
3aaa26c82f | ||
![]() |
6d314d10c5 | ||
![]() |
1ca36fa6c1 | ||
![]() |
818788da63 | ||
![]() |
0139cab13f | ||
![]() |
8dea682781 | ||
![]() |
1106aae76e | ||
![]() |
109b272bc2 | ||
![]() |
4c2b2ed1c6 | ||
![]() |
db13fe65b8 | ||
![]() |
8de14d4384 | ||
![]() |
728607643f | ||
![]() |
a923f657ac | ||
![]() |
80966dbff0 | ||
![]() |
048416163d | ||
![]() |
bea266b8ed | ||
![]() |
abaaa22adb | ||
![]() |
0c70146013 | ||
![]() |
212b2d4af9 | ||
![]() |
4dc106545b | ||
![]() |
2855ed4887 | ||
![]() |
9c019a65d5 | ||
![]() |
268effe62e | ||
![]() |
4da32a122b | ||
![]() |
6e546526b4 | ||
![]() |
c8de356d98 | ||
![]() |
e8dd0ae47d | ||
![]() |
cb24492371 | ||
![]() |
b50b7692b2 | ||
![]() |
9fe8e6f1ce | ||
![]() |
fbf9524a6c | ||
![]() |
caf29c3680 | ||
![]() |
e8a7112249 | ||
![]() |
f69be58f59 | ||
![]() |
1e7a37bcf9 | ||
![]() |
427d7c56ab | ||
![]() |
fe9c02062d | ||
![]() |
79611abdab | ||
![]() |
4c97072a3c | ||
![]() |
5d877d54d0 | ||
![]() |
0ad0a84ee1 | ||
![]() |
880975ae94 | ||
![]() |
93245ef143 | ||
![]() |
5f169bc884 | ||
![]() |
08d9fbf9bc | ||
![]() |
72d7616726 | ||
![]() |
eae15e3a83 | ||
![]() |
9fafb6d526 | ||
![]() |
59b3aaca42 | ||
![]() |
ab4e05be34 | ||
![]() |
dcdcbfd833 | ||
![]() |
438b31f270 | ||
![]() |
316a0c9bde | ||
![]() |
8477a1aba0 | ||
![]() |
41cb2d8cff | ||
![]() |
4b89b8260b | ||
![]() |
879dcbd1bf | ||
![]() |
0d9b2c75e4 | ||
![]() |
72a60f074b | ||
![]() |
53f09756b6 | ||
![]() |
97fb157b25 | ||
![]() |
12eaf08c88 | ||
![]() |
41ed9f0514 | ||
![]() |
848b87070c | ||
![]() |
a2ec1bc1d8 | ||
![]() |
c310c020bb | ||
![]() |
28568fd12f | ||
![]() |
d8e988105f | ||
![]() |
f71e6f9559 | ||
![]() |
d7956c19d3 | ||
![]() |
61cb72596e | ||
![]() |
1444421766 | ||
![]() |
76ce112002 | ||
![]() |
682bca7f9a | ||
![]() |
ff510cacc5 | ||
![]() |
ec89702137 | ||
![]() |
206de4df7a | ||
![]() |
f4aea1b3ab | ||
![]() |
7208540855 | ||
![]() |
7ca50c62e2 | ||
![]() |
d5d41a0d9e | ||
![]() |
60a5b7bc7a | ||
![]() |
c387f517b6 | ||
![]() |
2d9849e00a | ||
![]() |
adfda8adfd | ||
![]() |
1b6f539657 | ||
![]() |
a2f14e7713 | ||
![]() |
053cbe7558 | ||
![]() |
ea90a3ebfa | ||
![]() |
5e026e4b27 | ||
![]() |
a86d2c612a | ||
![]() |
a7151cd6c8 | ||
![]() |
fb444ad112 | ||
![]() |
e4b5755568 | ||
![]() |
95eb663347 | ||
![]() |
d75a708e6b | ||
![]() |
2ee201b520 | ||
![]() |
862d7ec807 | ||
![]() |
06d743cda9 | ||
![]() |
4922f7ba05 | ||
![]() |
3ef7106e0e | ||
![]() |
37af2ee1ec | ||
![]() |
fd0fd2c6d4 | ||
![]() |
2520c51aaf | ||
![]() |
49a8f813fe | ||
![]() |
6e42efd2b1 | ||
![]() |
6e3e632bbd | ||
![]() |
05b791653e | ||
![]() |
4ecb0b81ce | ||
![]() |
a68eef23aa | ||
![]() |
0a34949019 | ||
![]() |
9ff386bf48 | ||
![]() |
3d89d74bdc | ||
![]() |
a895eaaede | ||
![]() |
3a225c2180 | ||
![]() |
3f4dca21a8 | ||
![]() |
12771004cb | ||
![]() |
e23d939781 | ||
![]() |
a894f21ce5 | ||
![]() |
c721227a06 | ||
![]() |
99d1b5fbdf | ||
![]() |
a320d062fb | ||
![]() |
39cdce6960 | ||
![]() |
24ff29f74d | ||
![]() |
80696fec63 | ||
![]() |
f490643b25 | ||
![]() |
51ca89bc5c | ||
![]() |
d7d46e431b | ||
![]() |
697e26c25b | ||
![]() |
c7d3876fe6 | ||
![]() |
000938414f | ||
![]() |
c29c6b657a | ||
![]() |
a5486335a3 | ||
![]() |
a6b324bd3e | ||
![]() |
5702a18502 | ||
![]() |
9faa5d6fd9 | ||
![]() |
c13cec3d8a | ||
![]() |
b5e4b02d9c | ||
![]() |
bbfbc82c9d | ||
![]() |
d4b197b517 | ||
![]() |
e0984aa834 | ||
![]() |
ecb52651c3 | ||
![]() |
095c1e5b7f | ||
![]() |
05956c84a7 | ||
![]() |
81583d45ca | ||
![]() |
4571524e1c | ||
![]() |
681b616000 | ||
![]() |
229df1ce09 | ||
![]() |
67296d43c0 | ||
![]() |
bffff01acb | ||
![]() |
bcea79cd45 | ||
![]() |
1f9a17539c | ||
![]() |
af4fe48428 | ||
![]() |
adaf49e4f9 | ||
![]() |
22329e592e | ||
![]() |
3f58ceef95 | ||
![]() |
6494683c27 | ||
![]() |
34c11fdf10 | ||
![]() |
dc347d10b3 | ||
![]() |
e45b76c01e | ||
![]() |
79a5409a8e | ||
![]() |
853a9d8614 | ||
![]() |
27c1a7e94d | ||
![]() |
5d88263cd8 | ||
![]() |
2b7ef0dccc | ||
![]() |
a1158cdb1c | ||
![]() |
7cf3050da0 | ||
![]() |
686548cbb1 | ||
![]() |
35c8afd8c8 | ||
![]() |
082f9a0bd1 | ||
![]() |
f78e3f54ef | ||
![]() |
e0d1a58496 | ||
![]() |
cd59ab5406 | ||
![]() |
3d85936009 | ||
![]() |
a454b705a8 | ||
![]() |
4c4c59bf04 | ||
![]() |
2ee2d52035 | ||
![]() |
855a20fd1c | ||
![]() |
227d09a02c | ||
![]() |
8fd5c9f200 | ||
![]() |
0c482bda3c | ||
![]() |
7843a517bc | ||
![]() |
2cb193e8d6 | ||
![]() |
e457d53924 | ||
![]() |
68407ce2d8 | ||
![]() |
3bd35f53f2 | ||
![]() |
93ae79aa60 | ||
![]() |
b9d73edd66 | ||
![]() |
4c0b49135b | ||
![]() |
6ea797d0cd | ||
![]() |
8a4701143e | ||
![]() |
54b081c104 | ||
![]() |
8d5187432f | ||
![]() |
c57d496184 | ||
![]() |
bfa7ca639f | ||
![]() |
6334cfab0d | ||
![]() |
d9ab3a0f1c | ||
![]() |
c216eeafeb | ||
![]() |
01e3844ac4 | ||
![]() |
4a6c16e9dc | ||
![]() |
1c4f410052 | ||
![]() |
ce22ca5f71 | ||
![]() |
b5d1e03de1 | ||
![]() |
0ed7c5d969 | ||
![]() |
bb903b605a | ||
![]() |
1122cbf0b1 | ||
![]() |
b3c199870e | ||
![]() |
702808afa6 | ||
![]() |
51b0f7d28e | ||
![]() |
16ed266d11 | ||
![]() |
ca81e623db | ||
![]() |
6394a6dfc5 | ||
![]() |
163acf70a2 | ||
![]() |
0d9fc53a7d | ||
![]() |
b70f33d972 | ||
![]() |
bcd51779e6 | ||
![]() |
dbc9ab2d63 | ||
![]() |
c07eb2a6a0 | ||
![]() |
c8cecbf3cc | ||
![]() |
a968231750 | ||
![]() |
84b0864e7e | ||
![]() |
e5e494108e | ||
![]() |
a2126beca1 | ||
![]() |
4254eadce3 | ||
![]() |
2ab662b757 | ||
![]() |
c288741cab | ||
![]() |
141cb8d21b | ||
![]() |
0bdc828889 | ||
![]() |
24363170d3 | ||
![]() |
c8a7ee52bf | ||
![]() |
4260a0cfc3 | ||
![]() |
d6540b192e | ||
![]() |
d478387007 | ||
![]() |
1f627c5a30 | ||
![]() |
98394ceb8e | ||
![]() |
8f07082eba | ||
![]() |
41b1958626 | ||
![]() |
08d8d54b50 | ||
![]() |
01ca42648b | ||
![]() |
ab4fd41e55 | ||
![]() |
e47cd25275 | ||
![]() |
83cfcc63f1 | ||
![]() |
788b21556d | ||
![]() |
dcb73870d0 | ||
![]() |
6341846cea | ||
![]() |
f2bcb47986 | ||
![]() |
3fba4101e7 | ||
![]() |
d85236b9b0 | ||
![]() |
cba6993247 | ||
![]() |
c3848d0db4 | ||
![]() |
2e703f7aaa | ||
![]() |
c0ed8b7d91 | ||
![]() |
d0c1eb7488 | ||
![]() |
9d29e7f3ee | ||
![]() |
0bf778e270 | ||
![]() |
24cef3d4b6 | ||
![]() |
fafad0e3d5 | ||
![]() |
3eab2b6e7d | ||
![]() |
a65fc2e90b | ||
![]() |
227fb2cb75 | ||
![]() |
13591793de | ||
![]() |
0aca89781f | ||
![]() |
993bfaef2d | ||
![]() |
d63b3a2bce | ||
![]() |
c103ab5e28 | ||
![]() |
3ab750fecd | ||
![]() |
45959881ac | ||
![]() |
54ad9f0f4b | ||
![]() |
f39e8c9667 | ||
![]() |
c05da488f9 | ||
![]() |
7168b42bab | ||
![]() |
7560bf6429 | ||
![]() |
fcc97b0402 | ||
![]() |
6cb87a812f | ||
![]() |
330c280c78 | ||
![]() |
e14c79d57d | ||
![]() |
101fb1b540 | ||
![]() |
35810fe1bd | ||
![]() |
d8ea2fc795 | ||
![]() |
13c9094343 | ||
![]() |
da9d7eabdd | ||
![]() |
4422515f4b | ||
![]() |
7565ae15ef | ||
![]() |
2018be8e08 | ||
![]() |
5b802c2c9e | ||
![]() |
3a14025999 | ||
![]() |
d6797a1f1d | ||
![]() |
1d268f8b13 | ||
![]() |
b2d2719f90 | ||
![]() |
b1d449897a | ||
![]() |
0889276e24 | ||
![]() |
736b2a6ea9 | ||
![]() |
a2862bd102 | ||
![]() |
0311dae568 | ||
![]() |
1eb92fa39b | ||
![]() |
189700ecb3 | ||
![]() |
210c1ed902 | ||
![]() |
3da1a8d082 | ||
![]() |
efd633e76a | ||
![]() |
c3d7cd0da1 | ||
![]() |
ec6990fb71 | ||
![]() |
ee3182a2df | ||
![]() |
4a314394fd | ||
![]() |
132a3d4d1d | ||
![]() |
11abaf8055 | ||
![]() |
6f586c8b8f | ||
![]() |
1aed39a848 | ||
![]() |
8ec3cf6157 | ||
![]() |
87648af3ee | ||
![]() |
7a971e5842 | ||
![]() |
91ab8ac574 | ||
![]() |
60b6d4e7f2 | ||
![]() |
f3b8b2e336 | ||
![]() |
0e7627d1c4 | ||
![]() |
ffcf87d587 | ||
![]() |
535c8f904d | ||
![]() |
c244418aaa | ||
![]() |
4a3fd592e9 | ||
![]() |
ce8695c14c | ||
![]() |
65c174ed78 | ||
![]() |
240b12a7f3 | ||
![]() |
068b3bab42 | ||
![]() |
1bf8c7728e | ||
![]() |
32a3cb61c1 | ||
![]() |
b0de28208f | ||
![]() |
2f8205216a | ||
![]() |
9f11b83fa9 | ||
![]() |
f05da7cead | ||
![]() |
2718cb51eb | ||
![]() |
e633cd0385 | ||
![]() |
9c7de9b189 | ||
![]() |
19df34d941 | ||
![]() |
c3b7caaf13 | ||
![]() |
4843081089 | ||
![]() |
7b88c54d13 | ||
![]() |
2652a04be8 | ||
![]() |
abf0329cd9 | ||
![]() |
be980c6b1e | ||
![]() |
567cf7ea38 | ||
![]() |
c619c72211 | ||
![]() |
cb4ff24dc0 | ||
![]() |
4982b450fe | ||
![]() |
02d4428e39 | ||
![]() |
5e3cfecdea | ||
![]() |
876426c581 | ||
![]() |
37508676cf | ||
![]() |
af83161fed | ||
![]() |
8663d4f978 | ||
![]() |
7529bb0c64 | ||
![]() |
0e99ce91c1 | ||
![]() |
bec73109d6 | ||
![]() |
c7c830de80 | ||
![]() |
59dd54c049 | ||
![]() |
7cd5c904cb | ||
![]() |
47230198bb | ||
![]() |
edd17208c0 | ||
![]() |
5c0518053d | ||
![]() |
aa69234b76 | ||
![]() |
1f3be6340c | ||
![]() |
12b81ad754 | ||
![]() |
9b5a36814a | ||
![]() |
d77b900bd7 | ||
![]() |
fafdeb892e | ||
![]() |
0f8630fb2d | ||
![]() |
73e8a4e0ab | ||
![]() |
ad0827c59c | ||
![]() |
a66e0f2e11 | ||
![]() |
40f1880932 | ||
![]() |
56709a210b | ||
![]() |
27bb7179d6 | ||
![]() |
4b3d056212 | ||
![]() |
12d73e4167 | ||
![]() |
3f51943162 | ||
![]() |
f3be9a3305 | ||
![]() |
6a9f83cc2d | ||
![]() |
c08720ed1a | ||
![]() |
e96b4bf42e | ||
![]() |
9c3cc0cfa6 | ||
![]() |
bd71c8e6c9 | ||
![]() |
9719f5d57e | ||
![]() |
23e979717f | ||
![]() |
62021b0b40 | ||
![]() |
1b3422dab6 | ||
![]() |
9ca5d4a095 | ||
![]() |
eea5dc1918 | ||
![]() |
ae0f3f820e | ||
![]() |
299f842756 | ||
![]() |
0ab161c119 | ||
![]() |
70bd88c682 | ||
![]() |
60276cf1ff | ||
![]() |
54c7b5a261 | ||
![]() |
130bcf96ca | ||
![]() |
25016f5e27 | ||
![]() |
3f538865b5 | ||
![]() |
b07fba72e9 | ||
![]() |
9d8b737b1a | ||
![]() |
e1538b75f8 | ||
![]() |
74d1c47ce2 | ||
![]() |
982c43d595 | ||
![]() |
3eeb0bde9c | ||
![]() |
fc5d352431 | ||
![]() |
5e738fd62b | ||
![]() |
46be46d6ea | ||
![]() |
001b8c4222 | ||
![]() |
d0ad5cd15e | ||
![]() |
4e4d36a867 | ||
![]() |
2c4310d9ff | ||
![]() |
2883e541a9 | ||
![]() |
00d995d971 | ||
![]() |
a5ad5203df | ||
![]() |
47c9938d95 | ||
![]() |
ee3b0e614c | ||
![]() |
61332fdba1 | ||
![]() |
afd52a87dd | ||
![]() |
f9be184b86 | ||
![]() |
fe1833c5e1 | ||
![]() |
456672149b | ||
![]() |
d4cd512144 | ||
![]() |
a5d73654fc | ||
![]() |
bca49d3537 | ||
![]() |
ef38ac29ea | ||
![]() |
5f296eec38 | ||
![]() |
c46d1d9c7e | ||
![]() |
22237658ba | ||
![]() |
cf69964cd6 | ||
![]() |
44a89b2e87 | ||
![]() |
91b50d713e | ||
![]() |
a8734d7392 | ||
![]() |
d81faf7dad | ||
![]() |
abb884c156 | ||
![]() |
16d3962589 | ||
![]() |
5f12243fe4 | ||
![]() |
fe9f3a7088 | ||
![]() |
a57095ffe8 | ||
![]() |
37920d6a83 | ||
![]() |
da973299f2 | ||
![]() |
8260ebb32f | ||
![]() |
8328a622a2 | ||
![]() |
085b761f6b | ||
![]() |
8f937f0417 | ||
![]() |
7c25358607 | ||
![]() |
625c5302dd | ||
![]() |
8657976a6e | ||
![]() |
5dcf554bd4 | ||
![]() |
63bf1b8817 | ||
![]() |
95631ffdba | ||
![]() |
0810bd843c | ||
![]() |
d672c643b3 | ||
![]() |
a5c73ad8e5 | ||
![]() |
fb721e1d12 | ||
![]() |
84ec08616b | ||
![]() |
fa894289d4 | ||
![]() |
c70cba1e7c | ||
![]() |
b7c7197f1d | ||
![]() |
21585867b9 | ||
![]() |
99ffbc8c39 | ||
![]() |
fd13e559c6 | ||
![]() |
477e1dece9 | ||
![]() |
43d79d3a24 | ||
![]() |
cbe532cfc5 | ||
![]() |
6308eb6665 | ||
![]() |
593515784c | ||
![]() |
22ad950c5d | ||
![]() |
2d6e531e49 | ||
![]() |
677ae314fb | ||
![]() |
dade940632 | ||
![]() |
d0b9b955b8 | ||
![]() |
59047303c9 | ||
![]() |
15a9f0a104 | ||
![]() |
c07a983f05 | ||
![]() |
f3426456ad | ||
![]() |
853d72e4a5 | ||
![]() |
99a0236d21 | ||
![]() |
629850162a | ||
![]() |
c457b43ab1 | ||
![]() |
356deb5457 | ||
![]() |
e6080a7707 | ||
![]() |
4d9f427b19 | ||
![]() |
dd6feff13f | ||
![]() |
8f396fc348 | ||
![]() |
3fa9f1fc06 | ||
![]() |
be71254250 | ||
![]() |
1d9a154d5b | ||
![]() |
ea15b184c0 | ||
![]() |
a265fb4ae5 | ||
![]() |
56c24f5432 | ||
![]() |
9c656a0f9c | ||
![]() |
13a6d538a9 | ||
![]() |
2d9380a6fd | ||
![]() |
9c70971dc6 | ||
![]() |
1f3779f40e | ||
![]() |
4190a79835 | ||
![]() |
b01c4b6c6a | ||
![]() |
634c0916f6 | ||
![]() |
910bcdb017 | ||
![]() |
0eff63eed4 | ||
![]() |
b58ce1082e | ||
![]() |
911c2af044 | ||
![]() |
1a3a6f4a34 | ||
![]() |
1131caebe8 | ||
![]() |
3309c0e533 | ||
![]() |
7d9ed7e2ce | ||
![]() |
c7e30522bc | ||
![]() |
76627c2076 | ||
![]() |
ad14f38329 | ||
![]() |
129adc941a | ||
![]() |
2629366b23 | ||
![]() |
1c400043c6 | ||
![]() |
06833f6d8d | ||
![]() |
800685da68 | ||
![]() |
4cdd87d851 | ||
![]() |
2e726f1bb9 | ||
![]() |
a4e3f05601 | ||
![]() |
c4346c2624 | ||
![]() |
d389f4b5ca | ||
![]() |
ede5cd1f2c | ||
![]() |
0845d2ee76 | ||
![]() |
b9927b6f49 | ||
![]() |
ea016314b0 | ||
![]() |
9666244a87 | ||
![]() |
decdc857be | ||
![]() |
8b45d289bd | ||
![]() |
921a5cd632 | ||
![]() |
1da606dd8e | ||
![]() |
1bc1978c31 | ||
![]() |
25aa0cbab1 | ||
![]() |
0a8b8a89b4 | ||
![]() |
458cbfac59 | ||
![]() |
94c4c2f29f | ||
![]() |
93231f8168 | ||
![]() |
ec35e4f256 | ||
![]() |
5b927f18d0 | ||
![]() |
b04a7dab84 | ||
![]() |
0ed895cde7 | ||
![]() |
9663377374 | ||
![]() |
eed0edc12a | ||
![]() |
dc33d208b4 | ||
![]() |
86a2f923f6 | ||
![]() |
3c0cb3fd6a | ||
![]() |
460afc2e10 | ||
![]() |
d4d077818c | ||
![]() |
3039b54a75 | ||
![]() |
d378e4a70c | ||
![]() |
7ba61ced14 | ||
![]() |
4f9a75b390 | ||
![]() |
1cfc811506 | ||
![]() |
4c61838608 | ||
![]() |
690b719437 | ||
![]() |
fa71edd224 | ||
![]() |
5071245127 | ||
![]() |
d621115f9b | ||
![]() |
9e1789a6c2 | ||
![]() |
772a3ef893 | ||
![]() |
6d3321284c | ||
![]() |
09ff952d01 | ||
![]() |
e71acececc | ||
![]() |
f71400896b | ||
![]() |
b20f3ac29a | ||
![]() |
efe6954552 | ||
![]() |
046c0f9760 | ||
![]() |
59752d0cde | ||
![]() |
8304886cac | ||
![]() |
7cbd2848ce | ||
![]() |
75c5fa2ffe | ||
![]() |
df7d7b0ba0 | ||
![]() |
b742afb27f | ||
![]() |
f0bab665ce | ||
![]() |
ad1d82f7ad | ||
![]() |
f1dafc0373 | ||
![]() |
1c3d65dd52 | ||
![]() |
d253fc4dcd | ||
![]() |
b7651e214b | ||
![]() |
bcc7a61452 | ||
![]() |
8f2cbb5ea1 | ||
![]() |
a6bdaf1bbe | ||
![]() |
33d7ec1f04 | ||
![]() |
a7e4594fdc | ||
![]() |
1ca0a27d4d | ||
![]() |
44d27f9e4c | ||
![]() |
8e66c9b3c7 | ||
![]() |
2027a2f09f | ||
![]() |
8eb834f816 | ||
![]() |
82a0d7f3fb | ||
![]() |
6863e8ce8c | ||
![]() |
cca037421b | ||
![]() |
31bd75ff39 | ||
![]() |
d425ede401 | ||
![]() |
3c8179d987 | ||
![]() |
237aa22306 | ||
![]() |
28a72efc83 | ||
![]() |
d90fa8a266 | ||
![]() |
764f723829 | ||
![]() |
956bc0a397 | ||
![]() |
9d71bcbbb5 | ||
![]() |
e4c17deafb | ||
![]() |
0314d19e7e | ||
![]() |
6947ec3a28 | ||
![]() |
41efe12e9a | ||
![]() |
f037df0977 | ||
![]() |
44233c9cd1 | ||
![]() |
b2b0da036d | ||
![]() |
046dbdef63 | ||
![]() |
1a74ff4d45 | ||
![]() |
f41e852b0d | ||
![]() |
c2c6286537 | ||
![]() |
958c4fa7f2 | ||
![]() |
4318bb597f | ||
![]() |
a4a5d52e21 | ||
![]() |
6709b49cfa | ||
![]() |
6ad679ad66 | ||
![]() |
ffb24ceeee | ||
![]() |
0d39237b3c | ||
![]() |
d6d14b3f72 | ||
![]() |
af3c9be97c | ||
![]() |
eb397dacc4 | ||
![]() |
2b2cdf9847 | ||
![]() |
b78fa6f210 | ||
![]() |
ed05f57fc9 | ||
![]() |
6fdc5a84db | ||
![]() |
44d2bd4970 | ||
![]() |
69e4bbc301 | ||
![]() |
bdc0defa4a | ||
![]() |
b15cee4f10 | ||
![]() |
1c8bebcaf6 | ||
![]() |
42f34f763e | ||
![]() |
5d3eef7031 | ||
![]() |
463f7829d4 | ||
![]() |
462b023019 | ||
![]() |
991f6e0183 | ||
![]() |
8c8156f3aa | ||
![]() |
c3c06a1fcb | ||
![]() |
bd799a2680 | ||
![]() |
66f0a49b75 | ||
![]() |
ccf617ba85 | ||
![]() |
cc0f0bcc22 |
@ -11,7 +11,7 @@ omit =
|
|||||||
[report]
|
[report]
|
||||||
show_missing = true
|
show_missing = true
|
||||||
# TODO bump back to 79
|
# TODO bump back to 79
|
||||||
fail_under = 66
|
fail_under = 70
|
||||||
precision = 2
|
precision = 2
|
||||||
|
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@reflex-dev/reflex-team
|
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -2,7 +2,6 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
19
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement Request
|
||||||
|
about: Suggest an enhancement for an existing Reflex feature.
|
||||||
|
title: ''
|
||||||
|
labels: 'enhancement'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the Enhancement you want**
|
||||||
|
A clear and concise description of what the improvement does.
|
||||||
|
|
||||||
|
- Which feature do you want to improve? (and what problem does it have)
|
||||||
|
|
||||||
|
- What is the benefit of the enhancement?
|
||||||
|
|
||||||
|
- Show an example/usecase were the improvement are needed.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context here.
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest a new feature for Reflex
|
||||||
|
title: ''
|
||||||
|
labels: 'feature request'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the Features**
|
||||||
|
A clear and concise description of what the features does.
|
||||||
|
|
||||||
|
- What is the purpose of the feature?
|
||||||
|
|
||||||
|
- Show an example / use cases for the new feature.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context here.
|
4
.github/actions/setup_build_env/action.yml
vendored
4
.github/actions/setup_build_env/action.yml
vendored
@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
# Exit conditions:
|
# Exit conditions:
|
||||||
# - Python of version `python-version` is ready to be invoked as `python`.
|
# - Python of version `python-version` is ready to be invoked as `python`.
|
||||||
# - Poetry of version `poetry-version` is ready ot be invoked as `poetry`.
|
# - Poetry of version `poetry-version` is ready to be invoked as `poetry`.
|
||||||
# - If `run-poetry-install` is true, deps as defined in `pyproject.toml` will have been installed into the venv at `create-venv-at-path`.
|
# - If `run-poetry-install` is true, deps as defined in `pyproject.toml` will have been installed into the venv at `create-venv-at-path`.
|
||||||
|
|
||||||
name: 'Setup Reflex build environment'
|
name: 'Setup Reflex build environment'
|
||||||
@ -18,7 +18,7 @@ inputs:
|
|||||||
poetry-version:
|
poetry-version:
|
||||||
description: 'Poetry version to install'
|
description: 'Poetry version to install'
|
||||||
required: false
|
required: false
|
||||||
default: '1.3.1'
|
default: '1.8.3'
|
||||||
run-poetry-install:
|
run-poetry-install:
|
||||||
description: 'Whether to run poetry install on current dir'
|
description: 'Whether to run poetry install on current dir'
|
||||||
required: false
|
required: false
|
||||||
|
2
.github/codeql-config.yml
vendored
Normal file
2
.github/codeql-config.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
paths-ignore:
|
||||||
|
- "**/tests/**"
|
121
.github/workflows/benchmarks.yml
vendored
121
.github/workflows/benchmarks.yml
vendored
@ -5,7 +5,7 @@ on:
|
|||||||
types:
|
types:
|
||||||
- closed
|
- closed
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@ -15,22 +15,21 @@ defaults:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHONIOENCODING: 'utf8'
|
PYTHONIOENCODING: "utf8"
|
||||||
TELEMETRY_ENABLED: false
|
TELEMETRY_ENABLED: false
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: "--max_old_space_size=8192"
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reflex-web:
|
reflex-web:
|
||||||
# if: github.event.pull_request.merged == true
|
# if: github.event.pull_request.merged == true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
python-version: ['3.11.4']
|
python-version: ["3.12.8"]
|
||||||
node-version: ['16.x']
|
node-version: ["18.x"]
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@ -49,7 +48,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: reflex-dev/reflex-web
|
repository: reflex-dev/reflex-web
|
||||||
ref: reflex-ci
|
ref: main
|
||||||
path: reflex-web
|
path: reflex-web
|
||||||
|
|
||||||
- name: Install Requirements for reflex-web
|
- name: Install Requirements for reflex-web
|
||||||
@ -62,79 +61,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# Check that npm is home
|
# Check that npm is home
|
||||||
npm -v
|
npm -v
|
||||||
poetry run bash scripts/benchmarks/benchmarks.sh ./reflex-web prod
|
poetry run bash benchmarks/lighthouse.sh ./reflex-web prod
|
||||||
env:
|
env:
|
||||||
LHCI_GITHUB_APP_TOKEN: $
|
LHCI_GITHUB_APP_TOKEN: $
|
||||||
- name: Run Benchmarks
|
- name: Run Benchmarks
|
||||||
# Only run if the database creds are available in this context.
|
# Only run if the database creds are available in this context.
|
||||||
if: ${{ env.DATABASE_URL }}
|
run: poetry run python benchmarks/benchmark_lighthouse.py "$GITHUB_SHA" ./integration/benchmarks/.lighthouseci
|
||||||
run: poetry run python scripts/benchmarks/lighthouse_score_upload.py "$GITHUB_SHA" ./integration/benchmarks/.lighthouseci
|
|
||||||
env:
|
env:
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
|
||||||
simple-apps-benchmarks:
|
reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
env:
|
|
||||||
OUTPUT_FILE: benchmarks.json
|
|
||||||
timeout-minutes: 50
|
|
||||||
strategy:
|
|
||||||
# Prioritize getting more information out of the workflow (even if something fails)
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Show OS combos first in GUI
|
|
||||||
os: [ubuntu-latest, windows-latest, macos-12]
|
|
||||||
python-version: ['3.8.18', '3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
|
||||||
exclude:
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.10.13'
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.9.18'
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.8.18'
|
|
||||||
# keep only one python version for MacOS
|
|
||||||
- os: macos-latest
|
|
||||||
python-version: '3.8.18'
|
|
||||||
- os: macos-latest
|
|
||||||
python-version: '3.9.18'
|
|
||||||
- os: macos-latest
|
|
||||||
python-version: '3.10.13'
|
|
||||||
- os: macos-12
|
|
||||||
python-version: '3.12.0'
|
|
||||||
include:
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.10.11'
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.9.13'
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.8.10'
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
run-poetry-install: true
|
|
||||||
create-venv-at-path: .venv
|
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg2-binary
|
|
||||||
- name: Run benchmark tests
|
|
||||||
env:
|
|
||||||
APP_HARNESS_HEADLESS: 1
|
|
||||||
PYTHONUNBUFFERED: 1
|
|
||||||
run: |
|
|
||||||
poetry run pytest -v benchmarks/ --benchmark-json=${{ env.OUTPUT_FILE }} -s
|
|
||||||
- name: Upload benchmark results
|
|
||||||
# Only run if the database creds are available in this context.
|
|
||||||
if: ${{ env.DATABASE_URL }}
|
|
||||||
run:
|
|
||||||
poetry run python scripts/benchmarks/simple_app_benchmark_upload.py --os "${{ matrix.os }}"
|
|
||||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
|
||||||
--benchmark-json "${{ env.OUTPUT_FILE }}"
|
|
||||||
--db-url "${{ env.DATABASE_URL }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
|
||||||
--event-type "${{ github.event_name }}" --actor "${{ github.actor }}" --pr-id "${{ github.event.pull_request.id }}"
|
|
||||||
|
|
||||||
reflex-build-size:
|
|
||||||
if: github.event.pull_request.merged == true
|
if: github.event.pull_request.merged == true
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
@ -145,24 +81,21 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: 3.11.5
|
python-version: 3.12.8
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
- name: Install additional dependencies for DB access
|
|
||||||
run: poetry run uv pip install psycopg2-binary
|
|
||||||
- name: Build reflex
|
- name: Build reflex
|
||||||
run: |
|
run: |
|
||||||
poetry build
|
poetry build
|
||||||
- name: Upload benchmark results
|
- name: Upload benchmark results
|
||||||
# Only run if the database creds are available in this context.
|
# Only run if the database creds are available in this context.
|
||||||
if: ${{ env.DATABASE_URL }}
|
|
||||||
run:
|
run:
|
||||||
poetry run python scripts/benchmarks/benchmark_reflex_size.py --os ubuntu-latest
|
poetry run python benchmarks/benchmark_package_size.py --os ubuntu-latest
|
||||||
--python-version 3.11.5 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
|
--python-version 3.12.8 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||||
--db-url "${{ env.DATABASE_URL }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
--branch-name "${{ github.head_ref || github.ref_name }}"
|
||||||
--measurement-type "reflex-build" --path ./dist
|
--path ./dist
|
||||||
|
|
||||||
reflex-plus-dependency-size:
|
reflex-venv-size: # This job calculates the total size of Reflex and its dependencies
|
||||||
if: github.event.pull_request.merged == true
|
if: github.event.pull_request.merged == true
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
@ -170,13 +103,17 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest, macos-12]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
python-version: ['3.11.5']
|
python-version: ["3.12.8"]
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install Poetry
|
- name: Install Poetry
|
||||||
uses: snok/install-poetry@v1
|
uses: snok/install-poetry@v1
|
||||||
with:
|
with:
|
||||||
@ -197,14 +134,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
poetry run pip install uv
|
poetry run pip install uv
|
||||||
|
|
||||||
- name: Install additional dependencies for DB access
|
- name: calculate and upload size
|
||||||
run: poetry run uv pip install psycopg2-binary
|
|
||||||
|
|
||||||
- if: ${{ env.DATABASE_URL }}
|
|
||||||
name: calculate and upload size
|
|
||||||
run:
|
run:
|
||||||
poetry run python scripts/benchmarks/benchmark_reflex_size.py --os "${{ matrix.os }}"
|
poetry run python benchmarks/benchmark_package_size.py --os "${{ matrix.os }}"
|
||||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
||||||
--pr-id "${{ github.event.pull_request.id }}" --db-url "${{ env.DATABASE_URL }}"
|
--pr-id "${{ github.event.pull_request.id }}"
|
||||||
--branch-name "${{ github.head_ref || github.ref_name }}"
|
--branch-name "${{ github.head_ref || github.ref_name }}"
|
||||||
--measurement-type "reflex-package" --path ./.venv
|
--path ./.venv
|
||||||
|
10
.github/workflows/check_generated_pyi.yml
vendored
10
.github/workflows/check_generated_pyi.yml
vendored
@ -6,16 +6,16 @@ concurrency:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
# We don't just trigger on make_pyi.py and the components dir, because
|
# We don't just trigger on make_pyi.py and the components dir, because
|
||||||
# there are other things that can change the generator output
|
# there are other things that can change the generator output
|
||||||
# e.g. black version, reflex.Component, reflex.Var.
|
# e.g. black version, reflex.Component, reflex.Var.
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-generated-pyi-components:
|
check-generated-pyi-components:
|
||||||
@ -25,7 +25,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: '3.11.5'
|
python-version: "3.12.8"
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
- run: |
|
- run: |
|
||||||
|
40
.github/workflows/check_node_latest.yml
vendored
Normal file
40
.github/workflows/check_node_latest.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
name: integration-node-latest
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
TELEMETRY_ENABLED: false
|
||||||
|
REFLEX_USE_SYSTEM_NODE: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_latest_node:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.12.8"]
|
||||||
|
split_index: [1, 2]
|
||||||
|
node-version: ["node"]
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: |
|
||||||
|
poetry run uv pip install pyvirtualdisplay pillow pytest-split
|
||||||
|
poetry run playwright install --with-deps
|
||||||
|
- run: |
|
||||||
|
poetry run pytest tests/test_node_version.py
|
||||||
|
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
86
.github/workflows/check_outdated_dependencies.yml
vendored
Normal file
86
.github/workflows/check_outdated_dependencies.yml
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
name: check-outdated-dependencies
|
||||||
|
|
||||||
|
on:
|
||||||
|
push: # This will trigger the action when a pull request is opened or updated.
|
||||||
|
branches:
|
||||||
|
- "release/**" # This will trigger the action when any branch starting with "release/" is created.
|
||||||
|
workflow_dispatch: # Allow manual triggering if needed.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
|
||||||
|
- name: Check outdated backend dependencies
|
||||||
|
run: |
|
||||||
|
outdated=$(poetry show -oT)
|
||||||
|
echo "Outdated:"
|
||||||
|
echo "$outdated"
|
||||||
|
|
||||||
|
filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true)
|
||||||
|
|
||||||
|
if [ ! -z "$filtered_outdated" ]; then
|
||||||
|
echo "Outdated dependencies found:"
|
||||||
|
echo "$filtered_outdated"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All dependencies are up to date. (pyright and ruff are ignored)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: "3.10.16"
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
- name: Clone Reflex Website Repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: reflex-dev/reflex-web
|
||||||
|
ref: main
|
||||||
|
path: reflex-web
|
||||||
|
- name: Install Requirements for reflex-web
|
||||||
|
working-directory: ./reflex-web
|
||||||
|
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
||||||
|
- name: Install additional dependencies for DB access
|
||||||
|
run: poetry run uv pip install psycopg
|
||||||
|
- name: Init Website for reflex-web
|
||||||
|
working-directory: ./reflex-web
|
||||||
|
run: poetry run reflex init
|
||||||
|
- name: Run Website and Check for errors
|
||||||
|
run: |
|
||||||
|
poetry run bash scripts/integration.sh ./reflex-web dev
|
||||||
|
- name: Check outdated frontend dependencies
|
||||||
|
working-directory: ./reflex-web/.web
|
||||||
|
run: |
|
||||||
|
raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated)
|
||||||
|
outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true)
|
||||||
|
echo "Outdated:"
|
||||||
|
echo "$outdated"
|
||||||
|
|
||||||
|
# Ignore 3rd party dependencies that are not updated.
|
||||||
|
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images|ag-grid' || true)
|
||||||
|
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -z "$no_extra" ]; then
|
||||||
|
echo "Outdated dependencies found:"
|
||||||
|
echo "$filtered_outdated"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All dependencies are up to date. (3rd party packages are ignored)"
|
||||||
|
fi
|
103
.github/workflows/codeql.yml
vendored
Normal file
103
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL Advanced"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
schedule:
|
||||||
|
- cron: "36 7 * * 4"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze (${{ matrix.language }})
|
||||||
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
|
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||||
|
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
permissions:
|
||||||
|
# required for all workflows
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
# required to fetch internal or private CodeQL packs
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
# only required for workflows in private repositories
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- language: javascript-typescript
|
||||||
|
build-mode: none
|
||||||
|
- language: python
|
||||||
|
build-mode: none
|
||||||
|
- language: actions
|
||||||
|
build-mode: none
|
||||||
|
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||||
|
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||||
|
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||||
|
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||||
|
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||||
|
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||||
|
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||||
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||||
|
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||||
|
# or others). This is typically only required for manual builds.
|
||||||
|
# - name: Setup runtime (example)
|
||||||
|
# uses: actions/setup-example@v1
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
config-file: .github/codeql-config.yml
|
||||||
|
build-mode: ${{ matrix.build-mode }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
# If the analyze step fails for one of the languages you are analyzing with
|
||||||
|
# "We were unable to automatically build your code", modify the matrix above
|
||||||
|
# to set the build mode to "manual" for that language. Then modify this step
|
||||||
|
# to build your code.
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
- if: matrix.build-mode == 'manual'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||||
|
'languages you are analyzing, replace this with the commands to build' \
|
||||||
|
'your code, for example:'
|
||||||
|
echo ' make bootstrap'
|
||||||
|
echo ' make release'
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -13,5 +13,5 @@ jobs:
|
|||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@v4
|
uses: actions/dependency-review-action@v4
|
||||||
with:
|
with:
|
||||||
allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, HPND, ISC, MIT, MPL-2.0, PSF-2.0, Unlicense
|
allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, HPND, ISC, MIT, MPL-2.0, Unlicense, Python-2.0, Python-2.0.1, Apache-2.0 AND MIT, BSD-2-Clause AND BSD-3-Clause, Apache-2.0 AND BSD-3-Clause
|
||||||
allow-dependencies-licenses: 'pkg:pypi/lazy-loader'
|
allow-dependencies-licenses: 'pkg:pypi/lazy-loader'
|
28
.github/workflows/integration_app_harness.yml
vendored
28
.github/workflows/integration_app_harness.yml
vendored
@ -6,13 +6,13 @@ concurrency:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@ -22,9 +22,11 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
state_manager: ['redis', 'memory']
|
state_manager: ["redis", "memory"]
|
||||||
python-version: ['3.8.18', '3.11.5', '3.12.0']
|
python-version: ["3.11.11", "3.12.8", "3.13.1"]
|
||||||
runs-on: ubuntu-latest
|
split_index: [1, 2]
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
services:
|
services:
|
||||||
# Label used to access the service container
|
# Label used to access the service container
|
||||||
redis:
|
redis:
|
||||||
@ -45,16 +47,10 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
- run: poetry run uv pip install pyvirtualdisplay pillow
|
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
|
||||||
- name: Run app harness tests
|
- name: Run app harness tests
|
||||||
env:
|
env:
|
||||||
SCREENSHOT_DIR: /tmp/screenshots
|
|
||||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
||||||
run: |
|
run: |
|
||||||
poetry run pytest integration
|
poetry run playwright install chromium
|
||||||
- uses: actions/upload-artifact@v4
|
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
|
||||||
name: Upload failed test screenshots
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: failed_test_screenshots
|
|
||||||
path: /tmp/screenshots
|
|
||||||
|
156
.github/workflows/integration_tests.yml
vendored
156
.github/workflows/integration_tests.yml
vendored
@ -2,13 +2,13 @@ name: integration-tests
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
|
||||||
@ -27,14 +27,13 @@ env:
|
|||||||
# TODO: can we fix windows encoding natively within reflex? Bug above can hit real users too (less common, but possible)
|
# TODO: can we fix windows encoding natively within reflex? Bug above can hit real users too (less common, but possible)
|
||||||
# - Catch encoding errors when printing logs
|
# - Catch encoding errors when printing logs
|
||||||
# - Best effort print lines that contain illegal chars (map to some default char, etc.)
|
# - Best effort print lines that contain illegal chars (map to some default char, etc.)
|
||||||
PYTHONIOENCODING: 'utf8'
|
PYTHONIOENCODING: "utf8"
|
||||||
TELEMETRY_ENABLED: false
|
TELEMETRY_ENABLED: false
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: "--max_old_space_size=8192"
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
example-counter:
|
example-counter-and-nba-proxy:
|
||||||
env:
|
env:
|
||||||
OUTPUT_FILE: import_benchmark.json
|
OUTPUT_FILE: import_benchmark.json
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
@ -43,22 +42,18 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest, macos-12]
|
os: [ubuntu-latest, windows-latest]
|
||||||
python-version: ['3.8.18', '3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.10.13'
|
python-version: "3.11.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.9.18'
|
python-version: '3.10.16'
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.8.18'
|
|
||||||
include:
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: "3.11.9"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.10.11'
|
python-version: '3.10.11'
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.9.13'
|
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.8.10'
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@ -78,7 +73,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
poetry run uv pip install -r requirements.txt
|
poetry run uv pip install -r requirements.txt
|
||||||
- name: Install additional dependencies for DB access
|
- name: Install additional dependencies for DB access
|
||||||
run: poetry run uv pip install psycopg2-binary
|
run: poetry run uv pip install psycopg
|
||||||
- name: Check export --backend-only before init for counter example
|
- name: Check export --backend-only before init for counter example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/counter
|
||||||
run: |
|
run: |
|
||||||
@ -99,29 +94,25 @@ jobs:
|
|||||||
# Check that npm is home
|
# Check that npm is home
|
||||||
npm -v
|
npm -v
|
||||||
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
|
poetry run bash scripts/integration.sh ./reflex-examples/counter dev
|
||||||
- name: Measure and upload .web size
|
- name: Install requirements for nba proxy example
|
||||||
if: ${{ env.DATABASE_URL}}
|
working-directory: ./reflex-examples/nba-proxy
|
||||||
run:
|
run: |
|
||||||
poetry run python scripts/benchmarks/benchmark_reflex_size.py --os "${{ matrix.os }}"
|
poetry run uv pip install -r requirements.txt
|
||||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
- name: Install additional dependencies for DB access
|
||||||
--pr-id "${{ github.event.pull_request.id }}" --db-url "${{ env.DATABASE_URL }}"
|
run: poetry run uv pip install psycopg
|
||||||
--branch-name "${{ github.head_ref || github.ref_name }}"
|
- name: Check export --backend-only before init for nba-proxy example
|
||||||
--measurement-type "counter-app-dot-web" --path ./reflex-examples/counter/.web
|
working-directory: ./reflex-examples/nba-proxy
|
||||||
- name: Install hyperfine
|
run: |
|
||||||
run: cargo install hyperfine
|
poetry run reflex export --backend-only
|
||||||
- name: Benchmark imports
|
- name: Init Website for nba-proxy example
|
||||||
working-directory: ./reflex-examples/counter
|
working-directory: ./reflex-examples/nba-proxy
|
||||||
run: hyperfine --warmup 3 "export POETRY_VIRTUALENVS_PATH=../../.venv; poetry run python counter/counter.py" --show-output --export-json "${{ env.OUTPUT_FILE }}" --shell bash
|
run: |
|
||||||
- name: Upload Benchmarks
|
poetry run reflex init --loglevel debug
|
||||||
if : ${{ env.DATABASE_URL }}
|
- name: Run Website and Check for errors
|
||||||
run:
|
run: |
|
||||||
poetry run python scripts/benchmarks/benchmark_imports.py --os "${{ matrix.os }}"
|
# Check that npm is home
|
||||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
npm -v
|
||||||
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev
|
||||||
--db-url "${{ env.DATABASE_URL }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
|
||||||
--event-type "${{ github.event_name }}" --actor "${{ github.actor }}" --pr-id "${{ github.event.pull_request.id }}"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
reflex-web:
|
reflex-web:
|
||||||
@ -129,11 +120,11 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest, macos-12]
|
os: [ubuntu-latest]
|
||||||
python-version: ['3.10.11', '3.11.4']
|
python-version: ["3.11.11", "3.12.8"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REFLEX_WEB_WINDOWS_OVERRIDE: '1'
|
REFLEX_WEB_WINDOWS_OVERRIDE: "1"
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -152,9 +143,72 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Requirements for reflex-web
|
- name: Install Requirements for reflex-web
|
||||||
working-directory: ./reflex-web
|
working-directory: ./reflex-web
|
||||||
run: poetry run uv pip install -r requirements.txt
|
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
||||||
- name: Install additional dependencies for DB access
|
- name: Install additional dependencies for DB access
|
||||||
run: poetry run uv pip install psycopg2-binary
|
run: poetry run uv pip install psycopg
|
||||||
|
- name: Init Website for reflex-web
|
||||||
|
working-directory: ./reflex-web
|
||||||
|
run: poetry run reflex init
|
||||||
|
- name: Run Website and Check for errors
|
||||||
|
run: |
|
||||||
|
# Check that npm is home
|
||||||
|
npm -v
|
||||||
|
poetry run bash scripts/integration.sh ./reflex-web prod
|
||||||
|
|
||||||
|
rx-shout-from-template:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: "3.11.11"
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
- name: Create app directory
|
||||||
|
run: mkdir rx-shout-from-template
|
||||||
|
- name: Init reflex-web from template
|
||||||
|
run: poetry run reflex init --template https://github.com/masenf/rx_shout
|
||||||
|
working-directory: ./rx-shout-from-template
|
||||||
|
- name: ignore reflex pin in requirements
|
||||||
|
run: sed -i -e '/reflex==/d' requirements.txt
|
||||||
|
working-directory: ./rx-shout-from-template
|
||||||
|
- name: Install additional dependencies
|
||||||
|
run: poetry run uv pip install -r requirements.txt
|
||||||
|
working-directory: ./rx-shout-from-template
|
||||||
|
- name: Run Website and Check for errors
|
||||||
|
run: |
|
||||||
|
# Check that npm is home
|
||||||
|
npm -v
|
||||||
|
poetry run bash scripts/integration.sh ./rx-shout-from-template prod
|
||||||
|
|
||||||
|
reflex-web-macos:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# Note: py311 version chosen due to available arm64 darwin builds.
|
||||||
|
python-version: ["3.11.9", "3.12.8"]
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
- name: Clone Reflex Website Repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: reflex-dev/reflex-web
|
||||||
|
ref: main
|
||||||
|
path: reflex-web
|
||||||
|
- name: Install Requirements for reflex-web
|
||||||
|
working-directory: ./reflex-web
|
||||||
|
run: poetry run uv pip install -r requirements.txt
|
||||||
|
- name: Install additional dependencies for DB access
|
||||||
|
run: poetry run uv pip install psycopg
|
||||||
- name: Init Website for reflex-web
|
- name: Init Website for reflex-web
|
||||||
working-directory: ./reflex-web
|
working-directory: ./reflex-web
|
||||||
run: poetry run reflex init
|
run: poetry run reflex init
|
||||||
@ -163,11 +217,3 @@ jobs:
|
|||||||
# Check that npm is home
|
# Check that npm is home
|
||||||
npm -v
|
npm -v
|
||||||
poetry run bash scripts/integration.sh ./reflex-web prod
|
poetry run bash scripts/integration.sh ./reflex-web prod
|
||||||
- name: Measure and upload .web size
|
|
||||||
if: ${{ env.DATABASE_URL}}
|
|
||||||
run:
|
|
||||||
poetry run python scripts/benchmarks/benchmark_reflex_size.py --os "${{ matrix.os }}"
|
|
||||||
--python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
|
|
||||||
--pr-id "${{ github.event.pull_request.id }}"
|
|
||||||
--db-url "${{ env.DATABASE_URL }}" --branch-name "${{ github.head_ref || github.ref_name }}"
|
|
||||||
--measurement-type "reflex-web-dot-web" --path ./reflex-web/.web
|
|
||||||
|
2
.github/workflows/integration_tests_wsl.yml
vendored
2
.github/workflows/integration_tests_wsl.yml
vendored
@ -37,6 +37,8 @@ jobs:
|
|||||||
path: reflex-examples
|
path: reflex-examples
|
||||||
|
|
||||||
- uses: Vampire/setup-wsl@v3
|
- uses: Vampire/setup-wsl@v3
|
||||||
|
with:
|
||||||
|
distribution: Ubuntu-24.04
|
||||||
|
|
||||||
- name: Install Python
|
- name: Install Python
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
|
34
.github/workflows/performance.yml
vendored
Normal file
34
.github/workflows/performance.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: performance-tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main" # or "master"
|
||||||
|
paths-ignore:
|
||||||
|
- "**/*.md"
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
TELEMETRY_ENABLED: false
|
||||||
|
NODE_OPTIONS: "--max_old_space_size=8192"
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
APP_HARNESS_HEADLESS: 1
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
benchmarks:
|
||||||
|
name: Run benchmarks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: 3.12.8
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
- name: Run benchmarks
|
||||||
|
uses: CodSpeedHQ/action@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||||
|
run: poetry run pytest tests/benchmarks --codspeed
|
6
.github/workflows/pre-commit.yml
vendored
6
.github/workflows/pre-commit.yml
vendored
@ -6,12 +6,12 @@ concurrency:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
push:
|
push:
|
||||||
# Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
|
# Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
|
||||||
# also POST-commit on main also! In case there are mishandled merge conflicts / bad auto-resolves
|
# also POST-commit on main also! In case there are mishandled merge conflicts / bad auto-resolves
|
||||||
# when merging into main branch.
|
# when merging into main branch.
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
@ -23,7 +23,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
# running vs. one version of Python is OK
|
# running vs. one version of Python is OK
|
||||||
# i.e. ruff, black, etc.
|
# i.e. ruff, black, etc.
|
||||||
python-version: 3.11.5
|
python-version: 3.12.8
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
|
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
|
||||||
|
@ -28,5 +28,5 @@ jobs:
|
|||||||
# Run reflex init in a docker container
|
# Run reflex init in a docker container
|
||||||
|
|
||||||
# cwd is repo root
|
# cwd is repo root
|
||||||
docker build -f integration/init-test/Dockerfile -t reflex-init-test integration/init-test
|
docker build -f tests/integration/init-test/Dockerfile -t reflex-init-test tests/integration/init-test
|
||||||
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/integration/init-test/in_docker_test_script.sh
|
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/tests/integration/init-test/in_docker_test_script.sh
|
||||||
|
60
.github/workflows/unit_tests.yml
vendored
60
.github/workflows/unit_tests.yml
vendored
@ -6,13 +6,13 @@ concurrency:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- "**/*.md"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@ -27,24 +27,21 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-12]
|
os: [ubuntu-latest, windows-latest]
|
||||||
python-version: ['3.8.18', '3.9.18', '3.10.13', '3.11.5', '3.12.0']
|
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||||
# Windows is a bit behind on Python version availability in Github
|
# Windows is a bit behind on Python version availability in Github
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.10.13'
|
python-version: "3.11.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.9.18'
|
python-version: "3.10.16"
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.8.18'
|
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.10.11'
|
python-version: "3.11.9"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: '3.9.13'
|
python-version: "3.10.11"
|
||||||
- os: windows-latest
|
|
||||||
python-version: '3.8.10'
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
# Service containers to run with `runner-job`
|
# Service containers to run with `runner-job`
|
||||||
services:
|
services:
|
||||||
# Label used to access the service container
|
# Label used to access the service container
|
||||||
@ -69,17 +66,44 @@ jobs:
|
|||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: |
|
run: |
|
||||||
export PYTHONUNBUFFERED=1
|
export PYTHONUNBUFFERED=1
|
||||||
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
|
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||||
- name: Run unit tests w/ redis
|
- name: Run unit tests w/ redis
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
run: |
|
run: |
|
||||||
export PYTHONUNBUFFERED=1
|
export PYTHONUNBUFFERED=1
|
||||||
export REDIS_URL=redis://localhost:6379
|
export REDIS_URL=redis://localhost:6379
|
||||||
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
|
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||||
# Change to explicitly install v1 when reflex-hosting-cli is compatible with v2
|
# Change to explicitly install v1 when reflex-hosting-cli is compatible with v2
|
||||||
- name: Run unit tests w/ pydantic v1
|
- name: Run unit tests w/ pydantic v1
|
||||||
run: |
|
run: |
|
||||||
export PYTHONUNBUFFERED=1
|
export PYTHONUNBUFFERED=1
|
||||||
poetry run uv pip install "pydantic~=1.10"
|
poetry run uv pip install "pydantic~=1.10"
|
||||||
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
|
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||||
- run: poetry run coverage html
|
- name: Generate coverage report
|
||||||
|
run: poetry run coverage html
|
||||||
|
|
||||||
|
unit-tests-macos:
|
||||||
|
timeout-minutes: 30
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
|
||||||
|
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/setup_build_env
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
run-poetry-install: true
|
||||||
|
create-venv-at-path: .venv
|
||||||
|
- name: Run unit tests
|
||||||
|
run: |
|
||||||
|
export PYTHONUNBUFFERED=1
|
||||||
|
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||||
|
- name: Run unit tests w/ pydantic v1
|
||||||
|
run: |
|
||||||
|
export PYTHONUNBUFFERED=1
|
||||||
|
poetry run uv pip install "pydantic~=1.10"
|
||||||
|
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,6 +3,8 @@
|
|||||||
assets/external/*
|
assets/external/*
|
||||||
dist/*
|
dist/*
|
||||||
examples/
|
examples/
|
||||||
|
.web
|
||||||
|
.states
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.coverage
|
.coverage
|
||||||
@ -12,3 +14,5 @@ venv
|
|||||||
requirements.txt
|
requirements.txt
|
||||||
.pyi_generator_last_run
|
.pyi_generator_last_run
|
||||||
.pyi_generator_diff
|
.pyi_generator_diff
|
||||||
|
reflex.db
|
||||||
|
.codspeed
|
@ -3,19 +3,36 @@ fail_fast: true
|
|||||||
repos:
|
repos:
|
||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v0.1.0
|
rev: v0.9.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
args: [integration, reflex, tests]
|
args: [reflex, tests]
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: ["--fix", "--exit-non-zero-on-fix"]
|
args: ["--fix", "--exit-non-zero-on-fix"]
|
||||||
exclude: '^integration/benchmarks/'
|
exclude: '^integration/benchmarks/'
|
||||||
|
|
||||||
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
rev: v2.3.0
|
||||||
|
hooks:
|
||||||
|
- id: codespell
|
||||||
|
args: ["reflex"]
|
||||||
|
|
||||||
|
# Run pyi check before pyright because pyright can fail if pyi files are wrong.
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: update-pyi-files
|
||||||
|
name: update-pyi-files
|
||||||
|
always_run: true
|
||||||
|
language: system
|
||||||
|
require_serial: true
|
||||||
|
description: 'Update pyi files as needed'
|
||||||
|
entry: python3 scripts/make_pyi.py
|
||||||
|
|
||||||
- repo: https://github.com/RobertCraigie/pyright-python
|
- repo: https://github.com/RobertCraigie/pyright-python
|
||||||
rev: v1.1.313
|
rev: v1.1.393
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyright
|
- id: pyright
|
||||||
args: [integration, reflex, tests]
|
args: [reflex, tests]
|
||||||
language: system
|
language: system
|
||||||
|
|
||||||
- repo: https://github.com/terrencepreilly/darglint
|
- repo: https://github.com/terrencepreilly/darglint
|
||||||
@ -24,11 +41,3 @@ repos:
|
|||||||
- id: darglint
|
- id: darglint
|
||||||
exclude: '^reflex/reflex.py'
|
exclude: '^reflex/reflex.py'
|
||||||
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: update-pyi-files
|
|
||||||
name: update-pyi-files
|
|
||||||
always_run: true
|
|
||||||
language: system
|
|
||||||
description: 'Update pyi files as needed'
|
|
||||||
entry: python scripts/make_pyi.py
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
We as members, contributors, and leaders pledge to make participation in our
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
identity and expression, level of experience, education, socioeconomic status,
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
and orientation.
|
and orientation.
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
|
|||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
|
|
||||||
- Python >= 3.8
|
- Python >= 3.10
|
||||||
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
||||||
|
|
||||||
**1. Fork this repository:**
|
**1. Fork this repository:**
|
||||||
@ -69,7 +69,7 @@ In your `reflex` directory run make sure all the unit tests are still passing us
|
|||||||
This will fail if code coverage is below 70%.
|
This will fail if code coverage is below 70%.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
poetry run pytest tests --cov --no-cov-on-fail --cov-report=
|
poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
|
||||||
```
|
```
|
||||||
|
|
||||||
Next make sure all the following tests pass. This ensures that every new change has proper documentation and type checking.
|
Next make sure all the following tests pass. This ensures that every new change has proper documentation and type checking.
|
||||||
@ -87,7 +87,7 @@ poetry run ruff format .
|
|||||||
```
|
```
|
||||||
|
|
||||||
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
||||||
Note that pre-commit will only be installed when you use a Python version >= 3.8.
|
Note that pre-commit will only be installed when you use a Python version >= 3.10.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
pre-commit install
|
pre-commit install
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
### **✨ Performant, customizable web apps in pure Python. Deploy in seconds. ✨**
|
### **✨ Performant, customizable web apps in pure Python. Deploy in seconds. ✨**
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
@ -18,7 +17,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
|
|||||||
|
|
||||||
## ⚙️ Installation
|
## ⚙️ Installation
|
||||||
|
|
||||||
Open a terminal and run (Requires Python 3.8+):
|
Open a terminal and run (Requires Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -229,7 +228,7 @@ You can create a multi-page app by adding more pages.
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -250,7 +249,7 @@ We welcome contributions of any size! Below are some good ways to get started in
|
|||||||
- **GitHub Discussions**: A great way to talk about features you want added or things that are confusing/need clarification.
|
- **GitHub Discussions**: A great way to talk about features you want added or things that are confusing/need clarification.
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) are an excellent way to report bugs. Additionally, you can try and solve an existing issue and submit a PR.
|
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) are an excellent way to report bugs. Additionally, you can try and solve an existing issue and submit a PR.
|
||||||
|
|
||||||
We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
|
||||||
## All Thanks To Our Contributors:
|
## All Thanks To Our Contributors:
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
"""Runs the benchmarks and inserts the results into the database."""
|
"""Extracts the compile times from the JSON files in the specified directory and inserts them into the database."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from pathlib import Path
|
||||||
|
|
||||||
import psycopg2
|
from utils import send_data_to_posthog
|
||||||
|
|
||||||
|
|
||||||
def extract_stats_from_json(json_file: str) -> list[dict]:
|
def extract_stats_from_json(json_file: str) -> list[dict]:
|
||||||
@ -19,7 +19,7 @@ def extract_stats_from_json(json_file: str) -> list[dict]:
|
|||||||
Returns:
|
Returns:
|
||||||
list[dict]: The stats for each test.
|
list[dict]: The stats for each test.
|
||||||
"""
|
"""
|
||||||
with open(json_file, "r") as file:
|
with Path(json_file).open() as file:
|
||||||
json_data = json.load(file)
|
json_data = json.load(file)
|
||||||
|
|
||||||
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
|
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
|
||||||
@ -51,7 +51,6 @@ def extract_stats_from_json(json_file: str) -> list[dict]:
|
|||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
def insert_benchmarking_data(
|
||||||
db_connection_url: str,
|
|
||||||
os_type_version: str,
|
os_type_version: str,
|
||||||
python_version: str,
|
python_version: str,
|
||||||
performance_data: list[dict],
|
performance_data: list[dict],
|
||||||
@ -59,52 +58,33 @@ def insert_benchmarking_data(
|
|||||||
pr_title: str,
|
pr_title: str,
|
||||||
branch_name: str,
|
branch_name: str,
|
||||||
event_type: str,
|
event_type: str,
|
||||||
actor: str,
|
|
||||||
pr_id: str,
|
pr_id: str,
|
||||||
):
|
):
|
||||||
"""Insert the benchmarking data into the database.
|
"""Insert the benchmarking data into the database.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
db_connection_url: The URL to connect to the database.
|
|
||||||
os_type_version: The OS type and version to insert.
|
os_type_version: The OS type and version to insert.
|
||||||
python_version: The Python version to insert.
|
python_version: The Python version to insert.
|
||||||
performance_data: The performance data of reflex web to insert.
|
performance_data: The performance data of reflex web to insert.
|
||||||
commit_sha: The commit SHA to insert.
|
commit_sha: The commit SHA to insert.
|
||||||
pr_title: The PR title to insert.
|
pr_title: The PR title to insert.
|
||||||
branch_name: The name of the branch.
|
branch_name: The name of the branch.
|
||||||
event_type: Type of github event(push, pull request, etc)
|
event_type: Type of github event(push, pull request, etc).
|
||||||
actor: Username of the user that triggered the run.
|
|
||||||
pr_id: Id of the PR.
|
pr_id: Id of the PR.
|
||||||
"""
|
"""
|
||||||
# Serialize the JSON data
|
# Prepare the event data
|
||||||
simple_app_performance_json = json.dumps(performance_data)
|
properties = {
|
||||||
|
"os": os_type_version,
|
||||||
|
"python_version": python_version,
|
||||||
|
"distinct_id": commit_sha,
|
||||||
|
"pr_title": pr_title,
|
||||||
|
"branch_name": branch_name,
|
||||||
|
"event_type": event_type,
|
||||||
|
"performance": performance_data,
|
||||||
|
"pr_id": pr_id,
|
||||||
|
}
|
||||||
|
|
||||||
# Get the current timestamp
|
send_data_to_posthog("simple_app_benchmark", properties)
|
||||||
current_timestamp = datetime.now()
|
|
||||||
|
|
||||||
# Connect to the database and insert the data
|
|
||||||
with psycopg2.connect(db_connection_url) as conn, conn.cursor() as cursor:
|
|
||||||
insert_query = """
|
|
||||||
INSERT INTO simple_app_benchmarks (os, python_version, commit_sha, time, pr_title, branch_name, event_type, actor, performance, pr_id)
|
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
|
|
||||||
"""
|
|
||||||
cursor.execute(
|
|
||||||
insert_query,
|
|
||||||
(
|
|
||||||
os_type_version,
|
|
||||||
python_version,
|
|
||||||
commit_sha,
|
|
||||||
current_timestamp,
|
|
||||||
pr_title,
|
|
||||||
branch_name,
|
|
||||||
event_type,
|
|
||||||
actor,
|
|
||||||
simple_app_performance_json,
|
|
||||||
pr_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# Commit the transaction
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -124,11 +104,6 @@ def main():
|
|||||||
"--benchmark-json",
|
"--benchmark-json",
|
||||||
help="The JSON file containing the benchmark results.",
|
help="The JSON file containing the benchmark results.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--db-url",
|
|
||||||
help="The URL to connect to the database.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pr-title",
|
"--pr-title",
|
||||||
help="The PR title to insert into the database.",
|
help="The PR title to insert into the database.",
|
||||||
@ -143,11 +118,6 @@ def main():
|
|||||||
help="The github event type",
|
help="The github event type",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--actor",
|
|
||||||
help="Username of the user that triggered the run.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pr-id",
|
"--pr-id",
|
||||||
help="ID of the PR.",
|
help="ID of the PR.",
|
||||||
@ -162,7 +132,6 @@ def main():
|
|||||||
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
|
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
|
||||||
# Insert the data into the database
|
# Insert the data into the database
|
||||||
insert_benchmarking_data(
|
insert_benchmarking_data(
|
||||||
db_connection_url=args.db_url,
|
|
||||||
os_type_version=args.os,
|
os_type_version=args.os,
|
||||||
python_version=args.python_version,
|
python_version=args.python_version,
|
||||||
performance_data=cleaned_benchmark_results,
|
performance_data=cleaned_benchmark_results,
|
||||||
@ -170,7 +139,6 @@ def main():
|
|||||||
pr_title=pr_title,
|
pr_title=pr_title,
|
||||||
branch_name=args.branch_name,
|
branch_name=args.branch_name,
|
||||||
event_type=args.event_type,
|
event_type=args.event_type,
|
||||||
actor=args.actor,
|
|
||||||
pr_id=args.pr_id,
|
pr_id=args.pr_id,
|
||||||
)
|
)
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
"""Runs the benchmarks and inserts the results into the database."""
|
"""Extract and upload benchmarking data to PostHog."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from pathlib import Path
|
||||||
|
|
||||||
import psycopg2
|
from utils import send_data_to_posthog
|
||||||
|
|
||||||
|
|
||||||
def extract_stats_from_json(json_file: str) -> dict:
|
def extract_stats_from_json(json_file: str) -> dict:
|
||||||
@ -19,7 +19,7 @@ def extract_stats_from_json(json_file: str) -> dict:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: The stats for each test.
|
dict: The stats for each test.
|
||||||
"""
|
"""
|
||||||
with open(json_file, "r") as file:
|
with Path(json_file).open() as file:
|
||||||
json_data = json.load(file)
|
json_data = json.load(file)
|
||||||
|
|
||||||
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
|
# Load the JSON data if it is a string, otherwise assume it's already a dictionary
|
||||||
@ -34,59 +34,39 @@ def extract_stats_from_json(json_file: str) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def insert_benchmarking_data(
|
def insert_benchmarking_data(
|
||||||
db_connection_url: str,
|
|
||||||
os_type_version: str,
|
os_type_version: str,
|
||||||
python_version: str,
|
python_version: str,
|
||||||
performance_data: dict,
|
performance_data: dict,
|
||||||
commit_sha: str,
|
commit_sha: str,
|
||||||
pr_title: str,
|
pr_title: str,
|
||||||
branch_name: str,
|
branch_name: str,
|
||||||
event_type: str,
|
|
||||||
actor: str,
|
|
||||||
pr_id: str,
|
pr_id: str,
|
||||||
|
app_name: str,
|
||||||
):
|
):
|
||||||
"""Insert the benchmarking data into the database.
|
"""Insert the benchmarking data into the database.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
db_connection_url: The URL to connect to the database.
|
|
||||||
os_type_version: The OS type and version to insert.
|
os_type_version: The OS type and version to insert.
|
||||||
python_version: The Python version to insert.
|
python_version: The Python version to insert.
|
||||||
performance_data: The imports performance data to insert.
|
performance_data: The imports performance data to insert.
|
||||||
commit_sha: The commit SHA to insert.
|
commit_sha: The commit SHA to insert.
|
||||||
pr_title: The PR title to insert.
|
pr_title: The PR title to insert.
|
||||||
branch_name: The name of the branch.
|
branch_name: The name of the branch.
|
||||||
event_type: Type of github event(push, pull request, etc)
|
|
||||||
actor: Username of the user that triggered the run.
|
|
||||||
pr_id: Id of the PR.
|
pr_id: Id of the PR.
|
||||||
|
app_name: The name of the app being measured.
|
||||||
"""
|
"""
|
||||||
# Serialize the JSON data
|
properties = {
|
||||||
simple_app_performance_json = json.dumps(performance_data)
|
"os": os_type_version,
|
||||||
# Get the current timestamp
|
"python_version": python_version,
|
||||||
current_timestamp = datetime.now()
|
"distinct_id": commit_sha,
|
||||||
|
"pr_title": pr_title,
|
||||||
|
"branch_name": branch_name,
|
||||||
|
"pr_id": pr_id,
|
||||||
|
"performance": performance_data,
|
||||||
|
"app_name": app_name,
|
||||||
|
}
|
||||||
|
|
||||||
# Connect to the database and insert the data
|
send_data_to_posthog("import_benchmark", properties)
|
||||||
with psycopg2.connect(db_connection_url) as conn, conn.cursor() as cursor:
|
|
||||||
insert_query = """
|
|
||||||
INSERT INTO import_benchmarks (os, python_version, commit_sha, time, pr_title, branch_name, event_type, actor, performance, pr_id)
|
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
|
|
||||||
"""
|
|
||||||
cursor.execute(
|
|
||||||
insert_query,
|
|
||||||
(
|
|
||||||
os_type_version,
|
|
||||||
python_version,
|
|
||||||
commit_sha,
|
|
||||||
current_timestamp,
|
|
||||||
pr_title,
|
|
||||||
branch_name,
|
|
||||||
event_type,
|
|
||||||
actor,
|
|
||||||
simple_app_performance_json,
|
|
||||||
pr_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# Commit the transaction
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -106,11 +86,6 @@ def main():
|
|||||||
"--benchmark-json",
|
"--benchmark-json",
|
||||||
help="The JSON file containing the benchmark results.",
|
help="The JSON file containing the benchmark results.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--db-url",
|
|
||||||
help="The URL to connect to the database.",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pr-title",
|
"--pr-title",
|
||||||
help="The PR title to insert into the database.",
|
help="The PR title to insert into the database.",
|
||||||
@ -121,13 +96,8 @@ def main():
|
|||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--event-type",
|
"--app-name",
|
||||||
help="The github event type",
|
help="The name of the app measured.",
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--actor",
|
|
||||||
help="Username of the user that triggered the run.",
|
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@ -143,15 +113,13 @@ def main():
|
|||||||
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
|
cleaned_benchmark_results = extract_stats_from_json(args.benchmark_json)
|
||||||
# Insert the data into the database
|
# Insert the data into the database
|
||||||
insert_benchmarking_data(
|
insert_benchmarking_data(
|
||||||
db_connection_url=args.db_url,
|
|
||||||
os_type_version=args.os,
|
os_type_version=args.os,
|
||||||
python_version=args.python_version,
|
python_version=args.python_version,
|
||||||
performance_data=cleaned_benchmark_results,
|
performance_data=cleaned_benchmark_results,
|
||||||
commit_sha=args.commit_sha,
|
commit_sha=args.commit_sha,
|
||||||
pr_title=pr_title,
|
pr_title=pr_title,
|
||||||
branch_name=args.branch_name,
|
branch_name=args.branch_name,
|
||||||
event_type=args.event_type,
|
app_name=args.app_name,
|
||||||
actor=args.actor,
|
|
||||||
pr_id=args.pr_id,
|
pr_id=args.pr_id,
|
||||||
)
|
)
|
||||||
|
|
75
benchmarks/benchmark_lighthouse.py
Normal file
75
benchmarks/benchmark_lighthouse.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""Extracts the Lighthouse scores from the JSON files in the specified directory and inserts them into the database."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from utils import send_data_to_posthog
|
||||||
|
|
||||||
|
|
||||||
|
def insert_benchmarking_data(
|
||||||
|
lighthouse_data: dict,
|
||||||
|
commit_sha: str,
|
||||||
|
):
|
||||||
|
"""Insert the benchmarking data into the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lighthouse_data: The Lighthouse data to insert.
|
||||||
|
commit_sha: The commit SHA to insert.
|
||||||
|
"""
|
||||||
|
properties = {
|
||||||
|
"distinct_id": commit_sha,
|
||||||
|
"lighthouse_data": lighthouse_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send the data to PostHog
|
||||||
|
send_data_to_posthog("lighthouse_benchmark", properties)
|
||||||
|
|
||||||
|
|
||||||
|
def get_lighthouse_scores(directory_path: str | Path) -> dict:
|
||||||
|
"""Extracts the Lighthouse scores from the JSON files in the specified directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory_path (str): The path to the directory containing the JSON files.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The Lighthouse scores.
|
||||||
|
"""
|
||||||
|
scores = {}
|
||||||
|
directory_path = Path(directory_path)
|
||||||
|
try:
|
||||||
|
for filename in directory_path.iterdir():
|
||||||
|
if filename.suffix == ".json" and filename.stem != "manifest":
|
||||||
|
data = json.loads(filename.read_text())
|
||||||
|
# Extract scores and add them to the dictionary with the filename as key
|
||||||
|
scores[data["finalUrl"].replace("http://localhost:3000/", "/")] = {
|
||||||
|
"performance_score": data["categories"]["performance"]["score"],
|
||||||
|
"accessibility_score": data["categories"]["accessibility"]["score"],
|
||||||
|
"best_practices_score": data["categories"]["best-practices"][
|
||||||
|
"score"
|
||||||
|
],
|
||||||
|
"seo_score": data["categories"]["seo"]["score"],
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": e}
|
||||||
|
|
||||||
|
return scores
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Runs the benchmarks and inserts the results into the database."""
|
||||||
|
# Get the commit SHA and JSON directory from the command line arguments
|
||||||
|
commit_sha = sys.argv[1]
|
||||||
|
json_dir = sys.argv[2]
|
||||||
|
|
||||||
|
# Get the Lighthouse scores
|
||||||
|
lighthouse_scores = get_lighthouse_scores(json_dir)
|
||||||
|
|
||||||
|
# Insert the data into the database
|
||||||
|
insert_benchmarking_data(lighthouse_scores, commit_sha)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
135
benchmarks/benchmark_package_size.py
Normal file
135
benchmarks/benchmark_package_size.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
"""Checks the size of a specific directory and uploads result to Posthog."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from utils import get_directory_size, get_python_version, send_data_to_posthog
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_size(venv_path: Path, os_name):
|
||||||
|
"""Get the size of a specified package.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
venv_path: The path to the venv.
|
||||||
|
os_name: Name of os.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The total size of the package in bytes.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: when venv does not exist or python version is None.
|
||||||
|
"""
|
||||||
|
python_version = get_python_version(venv_path, os_name)
|
||||||
|
print("Python version:", python_version)
|
||||||
|
if python_version is None:
|
||||||
|
raise ValueError("Error: Failed to determine Python version.")
|
||||||
|
|
||||||
|
is_windows = "windows" in os_name
|
||||||
|
|
||||||
|
package_dir: Path = (
|
||||||
|
venv_path / "lib" / f"python{python_version}" / "site-packages"
|
||||||
|
if not is_windows
|
||||||
|
else venv_path / "Lib" / "site-packages"
|
||||||
|
)
|
||||||
|
if not package_dir.exists():
|
||||||
|
raise ValueError(
|
||||||
|
"Error: Virtual environment does not exist or is not activated."
|
||||||
|
)
|
||||||
|
|
||||||
|
total_size = get_directory_size(package_dir)
|
||||||
|
return total_size
|
||||||
|
|
||||||
|
|
||||||
|
def insert_benchmarking_data(
|
||||||
|
os_type_version: str,
|
||||||
|
python_version: str,
|
||||||
|
commit_sha: str,
|
||||||
|
pr_title: str,
|
||||||
|
branch_name: str,
|
||||||
|
pr_id: str,
|
||||||
|
path: str,
|
||||||
|
):
|
||||||
|
"""Insert the benchmarking data into PostHog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
os_type_version: The OS type and version to insert.
|
||||||
|
python_version: The Python version to insert.
|
||||||
|
commit_sha: The commit SHA to insert.
|
||||||
|
pr_title: The PR title to insert.
|
||||||
|
branch_name: The name of the branch.
|
||||||
|
pr_id: The id of the PR.
|
||||||
|
path: The path to the dir or file to check size.
|
||||||
|
"""
|
||||||
|
if "./dist" in path:
|
||||||
|
size = get_directory_size(Path(path))
|
||||||
|
else:
|
||||||
|
size = get_package_size(Path(path), os_type_version)
|
||||||
|
|
||||||
|
# Prepare the event data
|
||||||
|
properties = {
|
||||||
|
"path": path,
|
||||||
|
"os": os_type_version,
|
||||||
|
"python_version": python_version,
|
||||||
|
"distinct_id": commit_sha,
|
||||||
|
"pr_title": pr_title,
|
||||||
|
"branch_name": branch_name,
|
||||||
|
"pr_id": pr_id,
|
||||||
|
"size_mb": round(
|
||||||
|
size / (1024 * 1024), 3
|
||||||
|
), # save size in MB and round to 3 places
|
||||||
|
}
|
||||||
|
|
||||||
|
send_data_to_posthog("package_size", properties)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Runs the benchmarks and inserts the results."""
|
||||||
|
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--os", help="The OS type and version to insert into the database."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--python-version", help="The Python version to insert into the database."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--commit-sha", help="The commit SHA to insert into the database."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pr-title",
|
||||||
|
help="The PR title to insert into the database.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--branch-name",
|
||||||
|
help="The current branch",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pr-id",
|
||||||
|
help="The pr id",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--path",
|
||||||
|
help="The path to the vnenv.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
|
||||||
|
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
|
||||||
|
|
||||||
|
# Insert the data into the database
|
||||||
|
insert_benchmarking_data(
|
||||||
|
os_type_version=args.os,
|
||||||
|
python_version=args.python_version,
|
||||||
|
commit_sha=args.commit_sha,
|
||||||
|
pr_title=pr_title,
|
||||||
|
branch_name=args.branch_name,
|
||||||
|
pr_id=args.pr_id,
|
||||||
|
path=args.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
106
benchmarks/benchmark_web_size.py
Normal file
106
benchmarks/benchmark_web_size.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""Checks the size of a specific directory and uploads result to Posthog."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from utils import get_directory_size, send_data_to_posthog
|
||||||
|
|
||||||
|
|
||||||
|
def insert_benchmarking_data(
|
||||||
|
os_type_version: str,
|
||||||
|
python_version: str,
|
||||||
|
app_name: str,
|
||||||
|
commit_sha: str,
|
||||||
|
pr_title: str,
|
||||||
|
branch_name: str,
|
||||||
|
pr_id: str,
|
||||||
|
path: str,
|
||||||
|
):
|
||||||
|
"""Insert the benchmarking data into PostHog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app_name: The name of the app being measured.
|
||||||
|
os_type_version: The OS type and version to insert.
|
||||||
|
python_version: The Python version to insert.
|
||||||
|
commit_sha: The commit SHA to insert.
|
||||||
|
pr_title: The PR title to insert.
|
||||||
|
branch_name: The name of the branch.
|
||||||
|
pr_id: The id of the PR.
|
||||||
|
path: The path to the dir or file to check size.
|
||||||
|
"""
|
||||||
|
size = get_directory_size(Path(path))
|
||||||
|
|
||||||
|
# Prepare the event data
|
||||||
|
properties = {
|
||||||
|
"app_name": app_name,
|
||||||
|
"os": os_type_version,
|
||||||
|
"python_version": python_version,
|
||||||
|
"distinct_id": commit_sha,
|
||||||
|
"pr_title": pr_title,
|
||||||
|
"branch_name": branch_name,
|
||||||
|
"pr_id": pr_id,
|
||||||
|
"size_mb": round(
|
||||||
|
size / (1024 * 1024), 3
|
||||||
|
), # save size in MB and round to 3 places
|
||||||
|
}
|
||||||
|
|
||||||
|
send_data_to_posthog("web-size", properties)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Runs the benchmarks and inserts the results."""
|
||||||
|
parser = argparse.ArgumentParser(description="Run benchmarks and process results.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--os", help="The OS type and version to insert into the database."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--python-version", help="The Python version to insert into the database."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--commit-sha", help="The commit SHA to insert into the database."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pr-title",
|
||||||
|
help="The PR title to insert into the database.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--branch-name",
|
||||||
|
help="The current branch",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--app-name",
|
||||||
|
help="The name of the app measured.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pr-id",
|
||||||
|
help="The pr id",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--path",
|
||||||
|
help="The current path to app to check.",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Get the PR title from env or the args. For the PR merge or push event, there is no PR title, leaving it empty.
|
||||||
|
pr_title = args.pr_title or os.getenv("PR_TITLE", "")
|
||||||
|
|
||||||
|
# Insert the data into the database
|
||||||
|
insert_benchmarking_data(
|
||||||
|
app_name=args.app_name,
|
||||||
|
os_type_version=args.os,
|
||||||
|
python_version=args.python_version,
|
||||||
|
commit_sha=args.commit_sha,
|
||||||
|
pr_title=pr_title,
|
||||||
|
branch_name=args.branch_name,
|
||||||
|
pr_id=args.pr_id,
|
||||||
|
path=args.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,373 +0,0 @@
|
|||||||
"""Benchmark tests for apps with varying component numbers."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import time
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from benchmarks import WINDOWS_SKIP_REASON
|
|
||||||
from reflex import constants
|
|
||||||
from reflex.compiler import utils
|
|
||||||
from reflex.testing import AppHarness, chdir
|
|
||||||
from reflex.utils import build
|
|
||||||
|
|
||||||
|
|
||||||
def render_component(num: int):
|
|
||||||
"""Generate a number of components based on num.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
num: number of components to produce.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The rendered number of components.
|
|
||||||
"""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
return [
|
|
||||||
rx.fragment(
|
|
||||||
rx.box(
|
|
||||||
rx.accordion.root(
|
|
||||||
rx.accordion.item(
|
|
||||||
header="Full Ingredients", # type: ignore
|
|
||||||
content="Yes. It's built with accessibility in mind.", # type: ignore
|
|
||||||
font_size="3em",
|
|
||||||
),
|
|
||||||
rx.accordion.item(
|
|
||||||
header="Applications", # type: ignore
|
|
||||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore
|
|
||||||
),
|
|
||||||
collapsible=True,
|
|
||||||
variant="ghost",
|
|
||||||
width="25rem",
|
|
||||||
),
|
|
||||||
padding_top="20px",
|
|
||||||
),
|
|
||||||
rx.box(
|
|
||||||
rx.drawer.root(
|
|
||||||
rx.drawer.trigger(
|
|
||||||
rx.button("Open Drawer with snap points"), as_child=True
|
|
||||||
),
|
|
||||||
rx.drawer.overlay(),
|
|
||||||
rx.drawer.portal(
|
|
||||||
rx.drawer.content(
|
|
||||||
rx.flex(
|
|
||||||
rx.drawer.title("Drawer Content"),
|
|
||||||
rx.drawer.description("Drawer description"),
|
|
||||||
rx.drawer.close(
|
|
||||||
rx.button("Close Button"),
|
|
||||||
as_child=True,
|
|
||||||
),
|
|
||||||
direction="column",
|
|
||||||
margin="5em",
|
|
||||||
align_items="center",
|
|
||||||
),
|
|
||||||
top="auto",
|
|
||||||
height="100%",
|
|
||||||
flex_direction="column",
|
|
||||||
background_color="var(--green-3)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
snap_points=["148px", "355px", 1],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.box(
|
|
||||||
rx.callout(
|
|
||||||
"You will need admin privileges to install and access this application.",
|
|
||||||
icon="info",
|
|
||||||
size="3",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.box(
|
|
||||||
rx.table.root(
|
|
||||||
rx.table.header(
|
|
||||||
rx.table.row(
|
|
||||||
rx.table.column_header_cell("Full name"),
|
|
||||||
rx.table.column_header_cell("Email"),
|
|
||||||
rx.table.column_header_cell("Group"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.table.body(
|
|
||||||
rx.table.row(
|
|
||||||
rx.table.row_header_cell("Danilo Sousa"),
|
|
||||||
rx.table.cell("danilo@example.com"),
|
|
||||||
rx.table.cell("Developer"),
|
|
||||||
),
|
|
||||||
rx.table.row(
|
|
||||||
rx.table.row_header_cell("Zahra Ambessa"),
|
|
||||||
rx.table.cell("zahra@example.com"),
|
|
||||||
rx.table.cell("Admin"),
|
|
||||||
),
|
|
||||||
rx.table.row(
|
|
||||||
rx.table.row_header_cell("Jasper Eriksson"),
|
|
||||||
rx.table.cell("jasper@example.com"),
|
|
||||||
rx.table.cell("Developer"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
] * num
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithTenComponentsOnePage():
|
|
||||||
"""A reflex app with roughly 10 components on one page."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
|
||||||
return rx.center(rx.vstack(*render_component(1)))
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
app.add_page(index)
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithHundredComponentOnePage():
|
|
||||||
"""A reflex app with roughly 100 components on one page."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
|
||||||
return rx.center(rx.vstack(*render_component(100)))
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
app.add_page(index)
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithThousandComponentsOnePage():
|
|
||||||
"""A reflex app with roughly 1000 components on one page."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
|
||||||
return rx.center(rx.vstack(*render_component(1000)))
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
app.add_page(index)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_10_components(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Start Blank Template app at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
running AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp("app10components")
|
|
||||||
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial(
|
|
||||||
AppWithTenComponentsOnePage,
|
|
||||||
render_component=render_component, # type: ignore
|
|
||||||
),
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_100_components(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Start Blank Template app at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
running AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp("app100components")
|
|
||||||
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial(
|
|
||||||
AppWithHundredComponentOnePage,
|
|
||||||
render_component=render_component, # type: ignore
|
|
||||||
),
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_1000_components(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Create an app with 1000 components at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
an AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp("app1000components")
|
|
||||||
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial(
|
|
||||||
AppWithThousandComponentsOnePage,
|
|
||||||
render_component=render_component, # type: ignore
|
|
||||||
),
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying component numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_10_compile_time_cold(benchmark, app_with_10_components):
|
|
||||||
"""Test the compile time on a cold start for an app with roughly 10 components.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_10_components: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_10_components.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_10_components._initialize_app()
|
|
||||||
build.setup_frontend(app_with_10_components.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_10_components.app_path):
|
|
||||||
app_with_10_components.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=10)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying component numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_10_compile_time_warm(benchmark, app_with_10_components):
|
|
||||||
"""Test the compile time on a warm start for an app with roughly 10 components.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_10_components: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_10_components.app_path):
|
|
||||||
app_with_10_components._initialize_app()
|
|
||||||
build.setup_frontend(app_with_10_components.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_10_components.app_path):
|
|
||||||
app_with_10_components.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying component numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_100_compile_time_cold(benchmark, app_with_100_components):
|
|
||||||
"""Test the compile time on a cold start for an app with roughly 100 components.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_100_components: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_100_components.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_100_components._initialize_app()
|
|
||||||
build.setup_frontend(app_with_100_components.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_100_components.app_path):
|
|
||||||
app_with_100_components.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying component numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_100_compile_time_warm(benchmark, app_with_100_components):
|
|
||||||
"""Test the compile time on a warm start for an app with roughly 100 components.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_100_components: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_100_components.app_path):
|
|
||||||
app_with_100_components._initialize_app()
|
|
||||||
build.setup_frontend(app_with_100_components.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_100_components.app_path):
|
|
||||||
app_with_100_components.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying component numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_1000_compile_time_cold(benchmark, app_with_1000_components):
|
|
||||||
"""Test the compile time on a cold start for an app with roughly 1000 components.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_1000_components: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_1000_components.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_1000_components._initialize_app()
|
|
||||||
build.setup_frontend(app_with_1000_components.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_1000_components.app_path):
|
|
||||||
app_with_1000_components.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying component numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_1000_compile_time_warm(benchmark, app_with_1000_components):
|
|
||||||
"""Test the compile time on a warm start for an app with roughly 1000 components.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_1000_components: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_1000_components.app_path):
|
|
||||||
app_with_1000_components._initialize_app()
|
|
||||||
build.setup_frontend(app_with_1000_components.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_1000_components.app_path):
|
|
||||||
app_with_1000_components.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
@ -1,576 +0,0 @@
|
|||||||
"""Benchmark tests for apps with varying page numbers."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import time
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from benchmarks import WINDOWS_SKIP_REASON
|
|
||||||
from reflex import constants
|
|
||||||
from reflex.compiler import utils
|
|
||||||
from reflex.testing import AppHarness, chdir
|
|
||||||
from reflex.utils import build
|
|
||||||
|
|
||||||
|
|
||||||
def render_multiple_pages(app, num: int):
|
|
||||||
"""Add multiple pages based on num.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
app: The App object.
|
|
||||||
num: number of pages to render.
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
from rxconfig import config # type: ignore
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
docs_url = "https://reflex.dev/docs/getting-started/introduction/"
|
|
||||||
filename = f"{config.app_name}/{config.app_name}.py"
|
|
||||||
college = [
|
|
||||||
"Stanford University",
|
|
||||||
"Arizona",
|
|
||||||
"Arizona state",
|
|
||||||
"Baylor",
|
|
||||||
"Boston College",
|
|
||||||
"Boston University",
|
|
||||||
]
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
position: str
|
|
||||||
college: str
|
|
||||||
age: Tuple[int, int] = (18, 50)
|
|
||||||
salary: Tuple[int, int] = (0, 25000000)
|
|
||||||
|
|
||||||
comp1 = rx.center(
|
|
||||||
rx.theme_panel(),
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("Welcome to Reflex!", size="9"),
|
|
||||||
rx.text("Get started by editing ", rx.code(filename)),
|
|
||||||
rx.button(
|
|
||||||
"Check out our docs!",
|
|
||||||
on_click=lambda: rx.redirect(docs_url),
|
|
||||||
size="4",
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
spacing="7",
|
|
||||||
font_size="2em",
|
|
||||||
),
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
comp2 = rx.vstack(
|
|
||||||
rx.hstack(
|
|
||||||
rx.vstack(
|
|
||||||
rx.select(
|
|
||||||
["C", "PF", "SF", "PG", "SG"],
|
|
||||||
placeholder="Select a position. (All)",
|
|
||||||
on_change=State.set_position, # type: ignore
|
|
||||||
size="3",
|
|
||||||
),
|
|
||||||
rx.select(
|
|
||||||
college,
|
|
||||||
placeholder="Select a college. (All)",
|
|
||||||
on_change=State.set_college, # type: ignore
|
|
||||||
size="3",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.vstack(
|
|
||||||
rx.vstack(
|
|
||||||
rx.hstack(
|
|
||||||
rx.badge("Min Age: ", State.age[0]),
|
|
||||||
rx.divider(orientation="vertical"),
|
|
||||||
rx.badge("Max Age: ", State.age[1]),
|
|
||||||
),
|
|
||||||
rx.slider(
|
|
||||||
default_value=[18, 50],
|
|
||||||
min=18,
|
|
||||||
max=50,
|
|
||||||
on_value_commit=State.set_age, # type: ignore
|
|
||||||
),
|
|
||||||
align_items="left",
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.vstack(
|
|
||||||
rx.hstack(
|
|
||||||
rx.badge("Min Sal: ", State.salary[0] // 1000000, "M"),
|
|
||||||
rx.divider(orientation="vertical"),
|
|
||||||
rx.badge("Max Sal: ", State.salary[1] // 1000000, "M"),
|
|
||||||
),
|
|
||||||
rx.slider(
|
|
||||||
default_value=[0, 25000000],
|
|
||||||
min=0,
|
|
||||||
max=25000000,
|
|
||||||
on_value_commit=State.set_salary, # type: ignore
|
|
||||||
),
|
|
||||||
align_items="left",
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
spacing="4",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in range(1, num + 1):
|
|
||||||
if i % 2 == 1:
|
|
||||||
app.add_page(comp1, route=f"page{i}")
|
|
||||||
else:
|
|
||||||
app.add_page(comp2, route=f"page{i}")
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithOnePage():
|
|
||||||
"""A reflex app with one page."""
|
|
||||||
from rxconfig import config # type: ignore
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
docs_url = "https://reflex.dev/docs/getting-started/introduction/"
|
|
||||||
filename = f"{config.app_name}/{config.app_name}.py"
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
|
||||||
return rx.center(
|
|
||||||
rx.chakra.input(
|
|
||||||
id="token", value=State.router.session.client_token, is_read_only=True
|
|
||||||
),
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("Welcome to Reflex!", size="9"),
|
|
||||||
rx.text("Get started by editing ", rx.code(filename)),
|
|
||||||
rx.button(
|
|
||||||
"Check out our docs!",
|
|
||||||
on_click=lambda: rx.redirect(docs_url),
|
|
||||||
size="4",
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
spacing="7",
|
|
||||||
font_size="2em",
|
|
||||||
),
|
|
||||||
height="100vh",
|
|
||||||
)
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
app.add_page(index)
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithTenPages():
|
|
||||||
"""A reflex app with 10 pages."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
render_multiple_pages(app, 10)
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithHundredPages():
|
|
||||||
"""A reflex app with 100 pages."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
render_multiple_pages(app, 100)
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithThousandPages():
|
|
||||||
"""A reflex app with Thousand pages."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
render_multiple_pages(app, 1000)
|
|
||||||
|
|
||||||
|
|
||||||
def AppWithTenThousandPages():
|
|
||||||
"""A reflex app with ten thousand pages."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
render_multiple_pages(app, 10000)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_one_page(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Create an app with 10000 pages at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
an AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp(f"app1")
|
|
||||||
|
|
||||||
yield AppHarness.create(root=root, app_source=AppWithOnePage) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_ten_pages(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Create an app with 10 pages at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
an AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp(f"app10")
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial(
|
|
||||||
AppWithTenPages,
|
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_hundred_pages(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Create an app with 100 pages at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
an AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp(f"app100")
|
|
||||||
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial(
|
|
||||||
AppWithHundredPages,
|
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
|
||||||
),
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_thousand_pages(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Create an app with 1000 pages at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
an AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp(f"app1000")
|
|
||||||
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial( # type: ignore
|
|
||||||
AppWithThousandPages,
|
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
|
||||||
),
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_with_ten_thousand_pages(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Create an app with 10000 pages at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
running AppHarness instance
|
|
||||||
"""
|
|
||||||
root = tmp_path_factory.mktemp(f"app10000")
|
|
||||||
|
|
||||||
yield AppHarness.create(
|
|
||||||
root=root,
|
|
||||||
app_source=functools.partial(
|
|
||||||
AppWithTenThousandPages,
|
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
|
||||||
),
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_1_compile_time_cold(benchmark, app_with_one_page):
|
|
||||||
"""Test the compile time on a cold start for an app with 1 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_one_page: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_one_page.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_one_page._initialize_app()
|
|
||||||
build.setup_frontend(app_with_one_page.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_one_page.app_path):
|
|
||||||
app_with_one_page.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
app_with_one_page._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_1_compile_time_warm(benchmark, app_with_one_page):
|
|
||||||
"""Test the compile time on a warm start for an app with 1 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_one_page: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_one_page.app_path):
|
|
||||||
app_with_one_page._initialize_app()
|
|
||||||
build.setup_frontend(app_with_one_page.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_one_page.app_path):
|
|
||||||
app_with_one_page.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
app_with_one_page._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_10_compile_time_cold(benchmark, app_with_ten_pages):
|
|
||||||
"""Test the compile time on a cold start for an app with 10 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_ten_pages: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_ten_pages.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_ten_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_ten_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_ten_pages.app_path):
|
|
||||||
app_with_ten_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
app_with_ten_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_10_compile_time_warm(benchmark, app_with_ten_pages):
|
|
||||||
"""Test the compile time on a warm start for an app with 10 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_ten_pages: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_ten_pages.app_path):
|
|
||||||
app_with_ten_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_ten_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_ten_pages.app_path):
|
|
||||||
app_with_ten_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
app_with_ten_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_100_compile_time_cold(benchmark, app_with_hundred_pages):
|
|
||||||
"""Test the compile time on a cold start for an app with 100 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_hundred_pages: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_hundred_pages.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_hundred_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_hundred_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_hundred_pages.app_path):
|
|
||||||
app_with_hundred_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
app_with_hundred_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_100_compile_time_warm(benchmark, app_with_hundred_pages):
|
|
||||||
"""Test the compile time on a warm start for an app with 100 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_hundred_pages: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_hundred_pages.app_path):
|
|
||||||
app_with_hundred_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_hundred_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_hundred_pages.app_path):
|
|
||||||
app_with_hundred_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
app_with_hundred_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_1000_compile_time_cold(benchmark, app_with_thousand_pages):
|
|
||||||
"""Test the compile time on a cold start for an app with 1000 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_thousand_pages: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_thousand_pages.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_thousand_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_thousand_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_thousand_pages.app_path):
|
|
||||||
app_with_thousand_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
app_with_thousand_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_1000_compile_time_warm(benchmark, app_with_thousand_pages):
|
|
||||||
"""Test the compile time on a warm start for an app with 1000 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_thousand_pages: The app harness.
|
|
||||||
"""
|
|
||||||
with chdir(app_with_thousand_pages.app_path):
|
|
||||||
app_with_thousand_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_thousand_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_thousand_pages.app_path):
|
|
||||||
app_with_thousand_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
app_with_thousand_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_10000_compile_time_cold(benchmark, app_with_ten_thousand_pages):
|
|
||||||
"""Test the compile time on a cold start for an app with 10000 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_ten_thousand_pages: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup():
|
|
||||||
with chdir(app_with_ten_thousand_pages.app_path):
|
|
||||||
utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
|
|
||||||
app_with_ten_thousand_pages._initialize_app()
|
|
||||||
build.setup_frontend(app_with_ten_thousand_pages.app_path)
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_ten_thousand_pages.app_path):
|
|
||||||
app_with_ten_thousand_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
|
||||||
app_with_ten_thousand_pages._reload_state_module()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip
|
|
||||||
@pytest.mark.benchmark(
|
|
||||||
group="Compile time of varying page numbers",
|
|
||||||
min_rounds=5,
|
|
||||||
timer=time.perf_counter,
|
|
||||||
disable_gc=True,
|
|
||||||
warmup=False,
|
|
||||||
)
|
|
||||||
def test_app_10000_compile_time_warm(benchmark, app_with_ten_thousand_pages):
|
|
||||||
"""Test the compile time on a warm start for an app with 10000 page.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
benchmark: The benchmark fixture.
|
|
||||||
app_with_ten_thousand_pages: The app harness.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def benchmark_fn():
|
|
||||||
with chdir(app_with_ten_thousand_pages.app_path):
|
|
||||||
app_with_ten_thousand_pages.app_instance._compile()
|
|
||||||
|
|
||||||
benchmark(benchmark_fn)
|
|
||||||
app_with_ten_thousand_pages._reload_state_module()
|
|
74
benchmarks/utils.py
Normal file
74
benchmarks/utils.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
"""Utility functions for the benchmarks."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from httpx import HTTPError
|
||||||
|
|
||||||
|
|
||||||
|
def get_python_version(venv_path: Path, os_name):
|
||||||
|
"""Get the python version of python in a virtual env.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
venv_path: Path to virtual environment.
|
||||||
|
os_name: Name of os.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The python version.
|
||||||
|
"""
|
||||||
|
python_executable = (
|
||||||
|
venv_path / "bin" / "python"
|
||||||
|
if "windows" not in os_name
|
||||||
|
else venv_path / "Scripts" / "python.exe"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(
|
||||||
|
[str(python_executable), "--version"], stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
python_version = output.decode("utf-8").strip().split()[1]
|
||||||
|
return ".".join(python_version.split(".")[:-1])
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_directory_size(directory: Path):
|
||||||
|
"""Get the size of a directory in bytes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: The directory to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The size of the dir in bytes.
|
||||||
|
"""
|
||||||
|
total_size = 0
|
||||||
|
for dirpath, _, filenames in os.walk(directory):
|
||||||
|
for f in filenames:
|
||||||
|
fp = Path(dirpath) / f
|
||||||
|
total_size += fp.stat().st_size
|
||||||
|
return total_size
|
||||||
|
|
||||||
|
|
||||||
|
def send_data_to_posthog(event, properties):
|
||||||
|
"""Send data to PostHog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: The event to send.
|
||||||
|
properties: The properties to send.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPError: When there is an error sending data to PostHog.
|
||||||
|
"""
|
||||||
|
event_data = {
|
||||||
|
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
|
||||||
|
"event": event,
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
with httpx.Client() as client:
|
||||||
|
response = client.post("https://app.posthog.com/capture/", json=event_data)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPError(
|
||||||
|
f"Error sending data to PostHog: {response.status_code} - {response.text}"
|
||||||
|
)
|
@ -1,133 +1,30 @@
|
|||||||
# Reflex Docker Container
|
# Reflex Docker Examples
|
||||||
|
|
||||||
This example describes how to create and use a container image for Reflex with your own code.
|
This directory contains several examples of how to deploy Reflex apps using docker.
|
||||||
|
|
||||||
## Update Requirements
|
In all cases, ensure that your `requirements.txt` file is up to date and
|
||||||
|
includes the `reflex` package.
|
||||||
|
|
||||||
The `requirements.txt` includes the reflex package which is needed to install
|
## `simple-two-port`
|
||||||
Reflex framework. If you use additional packages in your project you have to add
|
|
||||||
this in the `requirements.txt` first. Copy the `Dockerfile`, `.dockerignore` and
|
|
||||||
the `requirements.txt` file in your project folder.
|
|
||||||
|
|
||||||
## Build Simple Reflex Container Image
|
The most basic production deployment exposes two HTTP ports and relies on an
|
||||||
|
existing load balancer to forward the traffic appropriately.
|
||||||
|
|
||||||
The main `Dockerfile` is intended to build a very simple, single container deployment that runs
|
## `simple-one-port`
|
||||||
the Reflex frontend and backend together, exposing ports 3000 and 8000.
|
|
||||||
|
|
||||||
To build your container image run the following command:
|
This deployment exports the frontend statically and serves it via a single HTTP
|
||||||
|
port using Caddy. This is useful for platforms that only support a single port
|
||||||
|
or where running a node server in the container is undesirable.
|
||||||
|
|
||||||
```bash
|
## `production-compose`
|
||||||
docker build -t reflex-app:latest .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Start Container Service
|
This deployment is intended for use with a standalone VPS that is only hosting a
|
||||||
|
single Reflex app. It provides the entire stack in a single `compose.yaml`
|
||||||
|
including a webserver, one or more backend instances, redis, and a postgres
|
||||||
|
database.
|
||||||
|
|
||||||
Finally, you can start your Reflex container service as follows:
|
## `production-app-platform`
|
||||||
|
|
||||||
```bash
|
This example deployment is intended for use with App hosting platforms, like
|
||||||
docker run -it --rm -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
|
Azure, AWS, or Google Cloud Run. It is the backend of the deployment, which
|
||||||
```
|
depends on a separately hosted redis instance and static frontend deployment.
|
||||||
|
|
||||||
It may take a few seconds for the service to become available.
|
|
||||||
|
|
||||||
Access your app at http://localhost:3000.
|
|
||||||
|
|
||||||
Note that this container has _no persistence_ and will lose all data when
|
|
||||||
stopped. You can use bind mounts or named volumes to persist the database and
|
|
||||||
uploaded_files directories as needed.
|
|
||||||
|
|
||||||
# Production Service with Docker Compose and Caddy
|
|
||||||
|
|
||||||
An example production deployment uses automatic TLS with Caddy serving static files
|
|
||||||
for the frontend and proxying requests to both the frontend and backend.
|
|
||||||
|
|
||||||
Copy the following files to your project directory:
|
|
||||||
* `compose.yaml`
|
|
||||||
* `compose.prod.yaml`
|
|
||||||
* `compose.tools.yaml`
|
|
||||||
* `prod.Dockerfile`
|
|
||||||
* `Caddy.Dockerfile`
|
|
||||||
* `Caddyfile`
|
|
||||||
|
|
||||||
The production app container, based on `prod.Dockerfile`, builds and exports the
|
|
||||||
frontend statically (to be served by Caddy). The resulting image only runs the
|
|
||||||
backend service.
|
|
||||||
|
|
||||||
The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
|
|
||||||
and `Caddyfile` into the container to configure the reverse proxy routes that will
|
|
||||||
forward requests to the backend service. Caddy will automatically provision TLS
|
|
||||||
for localhost or the domain specified in the environment variable `DOMAIN`.
|
|
||||||
|
|
||||||
This type of deployment should use less memory and be more performant since
|
|
||||||
nodejs is not required at runtime.
|
|
||||||
|
|
||||||
## Customize `Caddyfile` (optional)
|
|
||||||
|
|
||||||
If the app uses additional backend API routes, those should be added to the
|
|
||||||
`@backend_routes` path matcher to ensure they are forwarded to the backend.
|
|
||||||
|
|
||||||
## Build Reflex Production Service
|
|
||||||
|
|
||||||
During build, set `DOMAIN` environment variable to the domain where the app will
|
|
||||||
be hosted! (Do not include http or https, it will always use https).
|
|
||||||
|
|
||||||
**If `DOMAIN` is not provided, the service will default to `localhost`.**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose build
|
|
||||||
```
|
|
||||||
|
|
||||||
This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
|
|
||||||
service via `Caddy.Dockerfile`.
|
|
||||||
|
|
||||||
## Run Reflex Production Service
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
The app should be available at the specified domain via HTTPS. Certificate
|
|
||||||
provisioning will occur automatically and may take a few minutes.
|
|
||||||
|
|
||||||
### Data Persistence
|
|
||||||
|
|
||||||
Named docker volumes are used to persist the app database (`db-data`),
|
|
||||||
uploaded_files (`upload-data`), and caddy TLS keys and certificates
|
|
||||||
(`caddy-data`).
|
|
||||||
|
|
||||||
## More Robust Deployment
|
|
||||||
|
|
||||||
For a more robust deployment, consider bringing the service up with
|
|
||||||
`compose.prod.yaml` which includes postgres database and redis cache, allowing
|
|
||||||
the backend to run with multiple workers and service more requests.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Postgres uses its own named docker volume for data persistence.
|
|
||||||
|
|
||||||
## Admin Tools
|
|
||||||
|
|
||||||
When needed, the services in `compose.tools.yaml` can be brought up, providing
|
|
||||||
graphical database administration (Adminer on http://localhost:8080) and a
|
|
||||||
redis cache browser (redis-commander on http://localhost:8081). It is not recommended
|
|
||||||
to deploy these services if they are not in active use.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
# Container Hosting
|
|
||||||
|
|
||||||
Most container hosting services automatically terminate TLS and expect the app
|
|
||||||
to be listening on a single port (typically `$PORT`).
|
|
||||||
|
|
||||||
To host a Reflex app on one of these platforms, like Google Cloud Run, Render,
|
|
||||||
Railway, etc, use `app.Dockerfile` to build a single image containing a reverse
|
|
||||||
proxy that will serve that frontend as static files and proxy requests to the
|
|
||||||
backend for specific endpoints.
|
|
||||||
|
|
||||||
If the chosen platform does not support buildx and thus heredoc, you can copy
|
|
||||||
the Caddyfile configuration into a separate Caddyfile in the root of the
|
|
||||||
project.
|
|
5
docker-example/production-app-platform/.dockerignore
Normal file
5
docker-example/production-app-platform/.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.web
|
||||||
|
.git
|
||||||
|
__pycache__/*
|
||||||
|
Dockerfile
|
||||||
|
uploaded_files
|
65
docker-example/production-app-platform/Dockerfile
Normal file
65
docker-example/production-app-platform/Dockerfile
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# This docker file is intended to be used with container hosting services
|
||||||
|
#
|
||||||
|
# After deploying this image, get the URL pointing to the backend service
|
||||||
|
# and run API_URL=https://path-to-my-container.example.com reflex export frontend
|
||||||
|
# then copy the contents of `frontend.zip` to your static file server (github pages, s3, etc).
|
||||||
|
#
|
||||||
|
# Azure Static Web App example:
|
||||||
|
# npx @azure/static-web-apps-cli deploy --env production --app-location .web/_static
|
||||||
|
#
|
||||||
|
# For dynamic routes to function properly, ensure that 404s are redirected to /404 on the
|
||||||
|
# static file host (for github pages, this works out of the box; remember to create .nojekyll).
|
||||||
|
#
|
||||||
|
# For azure static web apps, add `staticwebapp.config.json` to to `.web/_static` with the following:
|
||||||
|
# {
|
||||||
|
# "responseOverrides": {
|
||||||
|
# "404": {
|
||||||
|
# "rewrite": "/404.html"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Note: many container hosting platforms require amd64 images, so when building on an M1 Mac
|
||||||
|
# for example, pass `docker build --platform=linux/amd64 ...`
|
||||||
|
|
||||||
|
# Stage 1: init
|
||||||
|
FROM python:3.13 as init
|
||||||
|
|
||||||
|
ARG uv=/root/.local/bin/uv
|
||||||
|
|
||||||
|
# Install `uv` for faster package bootstrapping
|
||||||
|
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
||||||
|
RUN /install.sh && rm /install.sh
|
||||||
|
|
||||||
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN mkdir -p /app/data /app/uploaded_files
|
||||||
|
|
||||||
|
# Create virtualenv which will be copied into final container
|
||||||
|
ENV VIRTUAL_ENV=/app/.venv
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
RUN $uv venv
|
||||||
|
|
||||||
|
# Install app requirements and reflex inside virtualenv
|
||||||
|
RUN $uv pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Deploy templates and prepare app
|
||||||
|
RUN reflex init
|
||||||
|
|
||||||
|
# Stage 2: copy artifacts into slim image
|
||||||
|
FROM python:3.13-slim
|
||||||
|
WORKDIR /app
|
||||||
|
RUN adduser --disabled-password --home /app reflex
|
||||||
|
COPY --chown=reflex --from=init /app /app
|
||||||
|
# Install libpq-dev for psycopg (skip if not using postgres).
|
||||||
|
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
||||||
|
USER reflex
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Needed until Reflex properly passes SIGTERM on backend.
|
||||||
|
STOPSIGNAL SIGKILL
|
||||||
|
|
||||||
|
# Always apply migrations before starting the backend.
|
||||||
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
|
exec reflex run --env prod --backend-only --backend-port ${PORT:-8000}
|
113
docker-example/production-app-platform/README.md
Normal file
113
docker-example/production-app-platform/README.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# production-app-platform
|
||||||
|
|
||||||
|
This example deployment is intended for use with App hosting platforms, like
|
||||||
|
Azure, AWS, or Google Cloud Run.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The production deployment consists of a few pieces:
|
||||||
|
* Backend container - built by `Dockerfile` Runs the Reflex backend
|
||||||
|
service on port 8000 and is scalable to multiple instances.
|
||||||
|
* Redis container - A single instance the standard `redis` docker image should
|
||||||
|
share private networking with the backend
|
||||||
|
* Static frontend - HTML/CSS/JS files that are hosted via a CDN or static file
|
||||||
|
server. This is not included in the docker image.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
These general steps do not cover the specifics of each platform, but all platforms should
|
||||||
|
support the concepts described here.
|
||||||
|
|
||||||
|
### Vnet
|
||||||
|
|
||||||
|
All containers in the deployment should be hooked up to the same virtual private
|
||||||
|
network so they can access the redis service and optionally the database server.
|
||||||
|
The vnet should not be exposed to the internet, use an ingress rule to terminate
|
||||||
|
TLS at the load balancer and forward the traffic to a backend service replica.
|
||||||
|
|
||||||
|
### Redis
|
||||||
|
|
||||||
|
Deploy a `redis` instance on the vnet.
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
The backend is built by the `Dockerfile` in this directory. When deploying the
|
||||||
|
backend, be sure to set REDIS_URL=redis://internal-redis-hostname to connect to
|
||||||
|
the redis service.
|
||||||
|
|
||||||
|
### Ingress
|
||||||
|
|
||||||
|
Configure the load balancer for the app to forward traffic to port 8000 on the
|
||||||
|
backend service replicas. Most platforms will generate an ingress hostname
|
||||||
|
automatically. Make sure when you access the ingress endpoint on `/ping` that it
|
||||||
|
returns "pong", indicating that the backend is up an available.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
The frontend should be hosted on a static file server or CDN.
|
||||||
|
|
||||||
|
**Important**: when exporting the frontend, set the API_URL environment variable
|
||||||
|
to the ingress hostname of the backend service.
|
||||||
|
|
||||||
|
If you will host the frontend from a path other than the root, set the
|
||||||
|
`FRONTEND_PATH` environment variable appropriately when exporting the frontend.
|
||||||
|
|
||||||
|
Most static hosts will automatically use the `/404.html` file to handle 404
|
||||||
|
errors. _This is essential for dynamic routes to work correctly._ Ensure that
|
||||||
|
missing routes return the `/404.html` content to the user if this is not the
|
||||||
|
default behavior.
|
||||||
|
|
||||||
|
_For Github Pages_: ensure the file `.nojekyll` is present in the root of the repo
|
||||||
|
to avoid special processing of underscore-prefix directories, like `_next`.
|
||||||
|
|
||||||
|
## Platform Notes
|
||||||
|
|
||||||
|
The following sections are currently a work in progress and may be incomplete.
|
||||||
|
|
||||||
|
### Azure
|
||||||
|
|
||||||
|
In the Azure load balancer, per-message deflate is not supported. Add the following
|
||||||
|
to your `rxconfig.py` to workaround this issue.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import uvicorn.workers
|
||||||
|
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
|
||||||
|
class NoWSPerMessageDeflate(uvicorn.workers.UvicornH11Worker):
|
||||||
|
CONFIG_KWARGS = {
|
||||||
|
**uvicorn.workers.UvicornH11Worker.CONFIG_KWARGS,
|
||||||
|
"ws_per_message_deflate": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
config = rx.Config(
|
||||||
|
app_name="my_app",
|
||||||
|
gunicorn_worker_class="rxconfig.NoWSPerMessageDeflate",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Persistent Storage
|
||||||
|
|
||||||
|
If you need to use a database or upload files, you cannot save them to the
|
||||||
|
container volume. Use Azure Files and mount it into the container at /app/uploaded_files.
|
||||||
|
|
||||||
|
#### Resource Types
|
||||||
|
|
||||||
|
* Create a new vnet with 10.0.0.0/16
|
||||||
|
* Create a new subnet for redis, database, and containers
|
||||||
|
* Deploy redis as a Container Instances
|
||||||
|
* Deploy database server as "Azure Database for PostgreSQL"
|
||||||
|
* Create a new database for the app
|
||||||
|
* Set db-url as a secret containing the db user/password connection string
|
||||||
|
* Deploy Storage account for uploaded files
|
||||||
|
* Enable access from the vnet and container subnet
|
||||||
|
* Create a new file share
|
||||||
|
* In the environment, create a new files share (get the storage key)
|
||||||
|
* Deploy the backend as a Container App
|
||||||
|
* Create a custom Container App Environment linked up to the same vnet as the redis container.
|
||||||
|
* Set REDIS_URL and DB_URL environment variables
|
||||||
|
* Add the volume from the environment
|
||||||
|
* Add the volume mount to the container
|
||||||
|
* Deploy the frontend as a Static Web App
|
@ -2,11 +2,11 @@
|
|||||||
# instance of a Reflex app.
|
# instance of a Reflex app.
|
||||||
|
|
||||||
# Stage 1: init
|
# Stage 1: init
|
||||||
FROM python:3.11 as init
|
FROM python:3.13 as init
|
||||||
|
|
||||||
ARG uv=/root/.cargo/bin/uv
|
ARG uv=/root/.local/bin/uv
|
||||||
|
|
||||||
# Install `uv` for faster package boostrapping
|
# Install `uv` for faster package bootstrapping
|
||||||
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
||||||
RUN /install.sh && rm /install.sh
|
RUN /install.sh && rm /install.sh
|
||||||
|
|
||||||
@ -35,17 +35,18 @@ RUN rm -rf .web && mkdir .web
|
|||||||
RUN mv /tmp/_static .web/_static
|
RUN mv /tmp/_static .web/_static
|
||||||
|
|
||||||
# Stage 2: copy artifacts into slim image
|
# Stage 2: copy artifacts into slim image
|
||||||
FROM python:3.11-slim
|
FROM python:3.13-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN adduser --disabled-password --home /app reflex
|
RUN adduser --disabled-password --home /app reflex
|
||||||
COPY --chown=reflex --from=init /app /app
|
COPY --chown=reflex --from=init /app /app
|
||||||
# Install libpq-dev for psycopg2 (skip if not using postgres).
|
# Install libpq-dev for psycopg (skip if not using postgres).
|
||||||
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
||||||
USER reflex
|
USER reflex
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
# Needed until Reflex properly passes SIGTERM on backend.
|
# Needed until Reflex properly passes SIGTERM on backend.
|
||||||
STOPSIGNAL SIGKILL
|
STOPSIGNAL SIGKILL
|
||||||
|
|
||||||
# Always apply migrations before starting the backend.
|
# Always apply migrations before starting the backend.
|
||||||
CMD reflex db migrate && reflex run --env prod --backend-only
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
|
exec reflex run --env prod --backend-only
|
75
docker-example/production-compose/README.md
Normal file
75
docker-example/production-compose/README.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# production-compose
|
||||||
|
|
||||||
|
This example production deployment uses automatic TLS with Caddy serving static
|
||||||
|
files for the frontend and proxying requests to both the frontend and backend.
|
||||||
|
It is intended for use with a standalone VPS that is only hosting a single
|
||||||
|
Reflex app.
|
||||||
|
|
||||||
|
The production app container (`Dockerfile`), builds and exports the frontend
|
||||||
|
statically (to be served by Caddy). The resulting image only runs the backend
|
||||||
|
service.
|
||||||
|
|
||||||
|
The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
|
||||||
|
and `Caddyfile` into the container to configure the reverse proxy routes that will
|
||||||
|
forward requests to the backend service. Caddy will automatically provision TLS
|
||||||
|
for localhost or the domain specified in the environment variable `DOMAIN`.
|
||||||
|
|
||||||
|
This type of deployment should use less memory and be more performant since
|
||||||
|
nodejs is not required at runtime.
|
||||||
|
|
||||||
|
## Customize `Caddyfile` (optional)
|
||||||
|
|
||||||
|
If the app uses additional backend API routes, those should be added to the
|
||||||
|
`@backend_routes` path matcher to ensure they are forwarded to the backend.
|
||||||
|
|
||||||
|
## Build Reflex Production Service
|
||||||
|
|
||||||
|
During build, set `DOMAIN` environment variable to the domain where the app will
|
||||||
|
be hosted! (Do not include http or https, it will always use https).
|
||||||
|
|
||||||
|
**If `DOMAIN` is not provided, the service will default to `localhost`.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOMAIN=example.com docker compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
|
||||||
|
service via `Caddy.Dockerfile`.
|
||||||
|
|
||||||
|
## Run Reflex Production Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOMAIN=example.com docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
The app should be available at the specified domain via HTTPS. Certificate
|
||||||
|
provisioning will occur automatically and may take a few minutes.
|
||||||
|
|
||||||
|
### Data Persistence
|
||||||
|
|
||||||
|
Named docker volumes are used to persist the app database (`db-data`),
|
||||||
|
uploaded_files (`upload-data`), and caddy TLS keys and certificates
|
||||||
|
(`caddy-data`).
|
||||||
|
|
||||||
|
## More Robust Deployment
|
||||||
|
|
||||||
|
For a more robust deployment, consider bringing the service up with
|
||||||
|
`compose.prod.yaml` which includes postgres database and redis cache, allowing
|
||||||
|
the backend to run with multiple workers and service more requests.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Postgres uses its own named docker volume for data persistence.
|
||||||
|
|
||||||
|
## Admin Tools
|
||||||
|
|
||||||
|
When needed, the services in `compose.tools.yaml` can be brought up, providing
|
||||||
|
graphical database administration (Adminer on http://localhost:8080) and a
|
||||||
|
redis cache browser (redis-commander on http://localhost:8081). It is not recommended
|
||||||
|
to deploy these services if they are not in active use.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
|
||||||
|
```
|
@ -15,7 +15,7 @@ services:
|
|||||||
|
|
||||||
app:
|
app:
|
||||||
environment:
|
environment:
|
||||||
DB_URL: postgresql+psycopg2://postgres:secret@db/postgres
|
DB_URL: postgresql+psycopg://postgres:secret@db/postgres
|
||||||
REDIS_URL: redis://redis:6379
|
REDIS_URL: redis://redis:6379
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
@ -12,7 +12,6 @@ services:
|
|||||||
DB_URL: sqlite:///data/reflex.db
|
DB_URL: sqlite:///data/reflex.db
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: prod.Dockerfile
|
|
||||||
volumes:
|
volumes:
|
||||||
- db-data:/app/data
|
- db-data:/app/data
|
||||||
- upload-data:/app/uploaded_files
|
- upload-data:/app/uploaded_files
|
3
docker-example/production-one-port/.dockerignore
Normal file
3
docker-example/production-one-port/.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.web
|
||||||
|
!.web/bun.lockb
|
||||||
|
!.web/package.json
|
14
docker-example/production-one-port/Caddyfile
Normal file
14
docker-example/production-one-port/Caddyfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
:{$PORT}
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
@backend_routes path /_event/* /ping /_upload /_upload/*
|
||||||
|
handle @backend_routes {
|
||||||
|
reverse_proxy localhost:8000
|
||||||
|
}
|
||||||
|
|
||||||
|
root * /srv
|
||||||
|
route {
|
||||||
|
try_files {path} {path}/ /404.html
|
||||||
|
file_server
|
||||||
|
}
|
62
docker-example/production-one-port/Dockerfile
Normal file
62
docker-example/production-one-port/Dockerfile
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# This Dockerfile is used to deploy a single-container Reflex app instance
|
||||||
|
# to services like Render, Railway, Heroku, GCP, and others.
|
||||||
|
|
||||||
|
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
||||||
|
ARG PORT=8080
|
||||||
|
# Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
|
||||||
|
ARG API_URL
|
||||||
|
|
||||||
|
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
||||||
|
# from a single exposed port, expecting TLS termination to be handled at the
|
||||||
|
# edge by the given platform.
|
||||||
|
FROM python:3.13 as builder
|
||||||
|
|
||||||
|
RUN mkdir -p /app/.web
|
||||||
|
RUN python -m venv /app/.venv
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install python app requirements and reflex in the container
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Install reflex helper utilities like bun/fnm/node
|
||||||
|
COPY rxconfig.py ./
|
||||||
|
RUN reflex init
|
||||||
|
|
||||||
|
# Install pre-cached frontend dependencies (if exist)
|
||||||
|
COPY *.web/bun.lockb *.web/package.json .web/
|
||||||
|
RUN if [ -f .web/bun.lockb ]; then cd .web && ~/.local/share/reflex/bun/bin/bun install --frozen-lockfile; fi
|
||||||
|
|
||||||
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG PORT API_URL
|
||||||
|
# Download other npm dependencies and compile frontend
|
||||||
|
RUN API_URL=${API_URL:-http://localhost:$PORT} reflex export --loglevel debug --frontend-only --no-zip && mv .web/_static/* /srv/ && rm -rf .web
|
||||||
|
|
||||||
|
|
||||||
|
# Final image with only necessary files
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
# Install Caddy and redis server inside image
|
||||||
|
RUN apt-get update -y && apt-get install -y caddy redis-server && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ARG PORT API_URL
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH" PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app /app
|
||||||
|
COPY --from=builder /srv /srv
|
||||||
|
|
||||||
|
# Needed until Reflex properly passes SIGTERM on backend.
|
||||||
|
STOPSIGNAL SIGKILL
|
||||||
|
|
||||||
|
EXPOSE $PORT
|
||||||
|
|
||||||
|
# Apply migrations before starting the backend.
|
||||||
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
|
caddy start && \
|
||||||
|
redis-server --daemonize yes && \
|
||||||
|
exec reflex run --env prod --backend-only
|
37
docker-example/production-one-port/README.md
Normal file
37
docker-example/production-one-port/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# production-one-port
|
||||||
|
|
||||||
|
This docker deployment runs Reflex in prod mode, exposing a single HTTP port:
|
||||||
|
* `8080` (`$PORT`) - Caddy server hosting the frontend statically and proxying requests to the backend.
|
||||||
|
|
||||||
|
The deployment also runs a local Redis server to store state for each user.
|
||||||
|
|
||||||
|
Conceptually it is similar to the `simple-one-port` example except it:
|
||||||
|
* has layer caching for python, reflex, and node dependencies
|
||||||
|
* uses multi-stage build to reduce the size of the final image
|
||||||
|
|
||||||
|
Using this method may be preferable for deploying in memory constrained
|
||||||
|
environments, because it serves a static frontend export, rather than running
|
||||||
|
the NextJS server via node.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker build -t reflex-production-one-port .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker run -p 8080:8080 reflex-production-one-port
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this container has _no persistence_ and will lose all data when
|
||||||
|
stopped. You can use bind mounts or named volumes to persist the database and
|
||||||
|
uploaded_files directories as needed.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This container should be used with an existing load balancer or reverse proxy to
|
||||||
|
terminate TLS.
|
||||||
|
|
||||||
|
It is also useful for deploying to simple app platforms, such as Render or Heroku.
|
@ -1 +0,0 @@
|
|||||||
reflex
|
|
5
docker-example/simple-one-port/.dockerignore
Normal file
5
docker-example/simple-one-port/.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.web
|
||||||
|
.git
|
||||||
|
__pycache__/*
|
||||||
|
Dockerfile
|
||||||
|
uploaded_files
|
14
docker-example/simple-one-port/Caddyfile
Normal file
14
docker-example/simple-one-port/Caddyfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
:{$PORT}
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
@backend_routes path /_event/* /ping /_upload /_upload/*
|
||||||
|
handle @backend_routes {
|
||||||
|
reverse_proxy localhost:8000
|
||||||
|
}
|
||||||
|
|
||||||
|
root * /srv
|
||||||
|
route {
|
||||||
|
try_files {path} {path}/ /404.html
|
||||||
|
file_server
|
||||||
|
}
|
@ -4,37 +4,19 @@
|
|||||||
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
||||||
# from a single exposed port, expecting TLS termination to be handled at the
|
# from a single exposed port, expecting TLS termination to be handled at the
|
||||||
# edge by the given platform.
|
# edge by the given platform.
|
||||||
FROM python:3.11
|
FROM python:3.13
|
||||||
|
|
||||||
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
||||||
ARG PORT=8080
|
ARG PORT=8080
|
||||||
# Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
|
# Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
|
||||||
ARG API_URL
|
ARG API_URL
|
||||||
ENV PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT}
|
ENV PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
# Install Caddy server inside image
|
# Install Caddy and redis server inside image
|
||||||
RUN apt-get update -y && apt-get install -y caddy && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update -y && apt-get install -y caddy redis-server && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Create a simple Caddyfile to serve as reverse proxy
|
|
||||||
RUN cat > Caddyfile <<EOF
|
|
||||||
:{\$PORT}
|
|
||||||
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
@backend_routes path /_event/* /ping /_upload /_upload/*
|
|
||||||
handle @backend_routes {
|
|
||||||
reverse_proxy localhost:8000
|
|
||||||
}
|
|
||||||
|
|
||||||
root * /srv
|
|
||||||
route {
|
|
||||||
try_files {path} {path}/ /404.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@ -54,4 +36,6 @@ EXPOSE $PORT
|
|||||||
|
|
||||||
# Apply migrations before starting the backend.
|
# Apply migrations before starting the backend.
|
||||||
CMD [ -d alembic ] && reflex db migrate; \
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
caddy start && reflex run --env prod --backend-only --loglevel debug
|
caddy start && \
|
||||||
|
redis-server --daemonize yes && \
|
||||||
|
exec reflex run --env prod --backend-only
|
36
docker-example/simple-one-port/README.md
Normal file
36
docker-example/simple-one-port/README.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# simple-one-port
|
||||||
|
|
||||||
|
This docker deployment runs Reflex in prod mode, exposing a single HTTP port:
|
||||||
|
* `8080` (`$PORT`) - Caddy server hosting the frontend statically and proxying requests to the backend.
|
||||||
|
|
||||||
|
The deployment also runs a local Redis server to store state for each user.
|
||||||
|
|
||||||
|
Using this method may be preferable for deploying in memory constrained
|
||||||
|
environments, because it serves a static frontend export, rather than running
|
||||||
|
the NextJS server via node.
|
||||||
|
|
||||||
|
For platforms which only terminate TLS to a single port, this container can be
|
||||||
|
deployed instead of the `simple-two-port` example.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker build -t reflex-simple-one-port .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker run -p 8080:8080 reflex-simple-one-port
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this container has _no persistence_ and will lose all data when
|
||||||
|
stopped. You can use bind mounts or named volumes to persist the database and
|
||||||
|
uploaded_files directories as needed.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This container should be used with an existing load balancer or reverse proxy to
|
||||||
|
terminate TLS.
|
||||||
|
|
||||||
|
It is also useful for deploying to simple app platforms, such as Render or Heroku.
|
5
docker-example/simple-two-port/.dockerignore
Normal file
5
docker-example/simple-two-port/.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.web
|
||||||
|
.git
|
||||||
|
__pycache__/*
|
||||||
|
Dockerfile
|
||||||
|
uploaded_files
|
@ -1,5 +1,8 @@
|
|||||||
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
||||||
FROM python:3.11
|
FROM python:3.13
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
|
||||||
|
ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -18,4 +21,6 @@ RUN reflex export --frontend-only --no-zip
|
|||||||
STOPSIGNAL SIGKILL
|
STOPSIGNAL SIGKILL
|
||||||
|
|
||||||
# Always apply migrations before starting the backend.
|
# Always apply migrations before starting the backend.
|
||||||
CMD [ -d alembic ] && reflex db migrate; reflex run --env prod
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
|
redis-server --daemonize yes && \
|
||||||
|
exec reflex run --env prod
|
44
docker-example/simple-two-port/README.md
Normal file
44
docker-example/simple-two-port/README.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# simple-two-port
|
||||||
|
|
||||||
|
This docker deployment runs Reflex in prod mode, exposing two HTTP ports:
|
||||||
|
* `3000` - node NextJS server using optimized production build
|
||||||
|
* `8000` - python gunicorn server hosting the Reflex backend
|
||||||
|
|
||||||
|
The deployment also runs a local Redis server to store state for each user.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker build -t reflex-simple-two-port .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker run -p 3000:3000 -p 8000:8000 reflex-simple-two-port
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this container has _no persistence_ and will lose all data when
|
||||||
|
stopped. You can use bind mounts or named volumes to persist the database and
|
||||||
|
uploaded_files directories as needed.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This container should be used with an existing load balancer or reverse proxy to
|
||||||
|
route traffic to the appropriate port inside the container.
|
||||||
|
|
||||||
|
For example, the following Caddyfile can be used to terminate TLS and forward
|
||||||
|
traffic to the frontend and backend from outside the container.
|
||||||
|
|
||||||
|
```
|
||||||
|
my-domain.com
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
@backend_routes path /_event/* /ping /_upload /_upload/*
|
||||||
|
handle @backend_routes {
|
||||||
|
reverse_proxy localhost:8000
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_proxy localhost:3000
|
||||||
|
```
|
261
docs/de/README.md
Normal file
261
docs/de/README.md
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
```diff
|
||||||
|
+ Suchst du nach Pynecone? Dann bist du hier in der richtigen Repository. Pynecone wurde in Reflex umbenannt. +
|
||||||
|
```
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### **✨ Performante, anpassbare Web-Apps in purem Python. Bereitstellung in Sekunden. ✨**
|
||||||
|
[](https://badge.fury.io/py/reflex)
|
||||||
|

|
||||||
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Reflex
|
||||||
|
|
||||||
|
Reflex ist eine Bibliothek, mit der man Full-Stack-Web-Applikationen in purem Python erstellen kann.
|
||||||
|
|
||||||
|
Wesentliche Merkmale:
|
||||||
|
* **Pures Python** - Schreibe dein Front- und Backend in Python, es gibt also keinen Grund, JavaScript zu lernen.
|
||||||
|
* **Volle Flexibilität** - Reflex ist einfach zu handhaben, kann aber auch für komplexe Anwendungen skaliert werden.
|
||||||
|
* **Sofortige Bereitstellung** - Nach dem Erstellen kannst du deine App mit einem [einzigen Befehl](https://reflex.dev/docs/hosting/deploy-quick-start/) bereitstellen oder auf deinem eigenen Server hosten.
|
||||||
|
|
||||||
|
Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) erfahren Sie, wie Reflex unter der Haube funktioniert.
|
||||||
|
|
||||||
|
## ⚙️ Installation
|
||||||
|
|
||||||
|
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install reflex
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🥳 Erstelle deine erste App
|
||||||
|
|
||||||
|
Die Installation von `reflex` installiert auch das `reflex`-Kommandozeilen-Tool.
|
||||||
|
|
||||||
|
Teste, ob die Installation erfolgreich war, indem du ein neues Projekt erstellst. (Ersetze `my_app_name` durch deinen Projektnamen):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir my_app_name
|
||||||
|
cd my_app_name
|
||||||
|
reflex init
|
||||||
|
```
|
||||||
|
|
||||||
|
Dieser Befehl initialisiert eine Vorlage in deinem neuen Verzeichnis.
|
||||||
|
|
||||||
|
Du kannst diese App im Entwicklungsmodus ausführen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
reflex run
|
||||||
|
```
|
||||||
|
|
||||||
|
Du solltest deine App unter http://localhost:3000 laufen sehen.
|
||||||
|
|
||||||
|
Nun kannst du den Quellcode in `my_app_name/my_app_name.py` ändern. Reflex hat schnelle Aktualisierungen, sodass du deine Änderungen sofort siehst, wenn du deinen Code speicherst.
|
||||||
|
|
||||||
|
|
||||||
|
## 🫧 Beispiel-App
|
||||||
|
|
||||||
|
Lass uns ein Beispiel durchgehen: die Erstellung einer Benutzeroberfläche für die Bildgenerierung mit [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Zur Vereinfachung rufen wir einfach die [OpenAI-API](https://platform.openai.com/docs/api-reference/authentication) auf, aber du könntest dies auch durch ein lokal ausgeführtes ML-Modell ersetzen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="Eine Benutzeroberfläche für DALL·E, die im Prozess der Bildgenerierung gezeigt wird." width="550" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Hier ist der komplette Code, um dies zu erstellen. Das alles wird in einer Python-Datei gemacht!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
import reflex as rx
|
||||||
|
import openai
|
||||||
|
|
||||||
|
openai_client = openai.OpenAI()
|
||||||
|
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
"""Der Zustand der App."""
|
||||||
|
|
||||||
|
prompt = ""
|
||||||
|
image_url = ""
|
||||||
|
processing = False
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
def get_image(self):
|
||||||
|
"""Hole das Bild aus dem Prompt."""
|
||||||
|
if self.prompt == "":
|
||||||
|
return rx.window_alert("Prompt Empty")
|
||||||
|
|
||||||
|
self.processing, self.complete = True, False
|
||||||
|
yield
|
||||||
|
response = openai_client.images.generate(
|
||||||
|
prompt=self.prompt, n=1, size="1024x1024"
|
||||||
|
)
|
||||||
|
self.image_url = response.data[0].url
|
||||||
|
self.processing, self.complete = False, True
|
||||||
|
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.center(
|
||||||
|
rx.vstack(
|
||||||
|
rx.heading("DALL-E", font_size="1.5em"),
|
||||||
|
rx.input(
|
||||||
|
placeholder="Enter a prompt..",
|
||||||
|
on_blur=State.set_prompt,
|
||||||
|
width="25em",
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Generate Image",
|
||||||
|
on_click=State.get_image,
|
||||||
|
width="25em",
|
||||||
|
loading=State.processing
|
||||||
|
),
|
||||||
|
rx.cond(
|
||||||
|
State.complete,
|
||||||
|
rx.image(src=State.image_url, width="20em"),
|
||||||
|
),
|
||||||
|
align="center",
|
||||||
|
),
|
||||||
|
width="100%",
|
||||||
|
height="100vh",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Füge Zustand und Seite zur App hinzu.
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index, title="Reflex:DALL-E")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Schauen wir uns das mal genauer an.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="docs/images/dalle_colored_code_example.png" alt="Erläuterung der Unterschiede zwischen Backend- und Frontend-Teilen der DALL-E-App." width="900" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
### **Reflex-UI**
|
||||||
|
|
||||||
|
Fangen wir mit der Benutzeroberfläche an.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def index():
|
||||||
|
return rx.center(
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Diese `index`-Funktion definiert das Frontend der App.
|
||||||
|
|
||||||
|
Wir verwenden verschiedene Komponenten wie `center`, `vstack`, `input` und `button`, um das Frontend zu erstellen. Komponenten können ineinander verschachtelt werden, um komplexe Layouts zu erstellen. Und du kannst Schlüsselwortargumente verwenden, um sie mit der vollen Kraft von CSS zu stylen.
|
||||||
|
|
||||||
|
Reflex wird mit [über 60 eingebauten Komponenten](https://reflex.dev/docs/library) geliefert, die dir den Einstieg erleichtern. Wir fügen aktiv weitere Komponenten hinzu, und es ist einfach, [eigene Komponenten zu erstellen](https://reflex.dev/docs/wrapping-react/overview/).
|
||||||
|
|
||||||
|
### **State**
|
||||||
|
|
||||||
|
Reflex stellt deine Benutzeroberfläche als Funktion deines Zustands dar.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class State(rx.State):
|
||||||
|
"""Der Zustand der App."""
|
||||||
|
prompt = ""
|
||||||
|
image_url = ""
|
||||||
|
processing = False
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Zustand definiert alle Variablen (genannt Vars) in einer App, die sich ändern können, und die Funktionen, die sie ändern.
|
||||||
|
|
||||||
|
Hier besteht der Zustand aus einem `prompt` und einer `image_url`. Es gibt auch die Booleans `processing` und `complete`, um anzuzeigen, wann der Button deaktiviert werden soll (während der Bildgenerierung) und wann das resultierende Bild angezeigt werden soll.
|
||||||
|
|
||||||
|
### **Event-Handler**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_image(self):
|
||||||
|
"""Hole das Bild aus dem Prompt."""
|
||||||
|
if self.prompt == "":
|
||||||
|
return rx.window_alert("Prompt Empty")
|
||||||
|
|
||||||
|
self.processing, self.complete = True, False
|
||||||
|
yield
|
||||||
|
response = openai_client.images.generate(
|
||||||
|
prompt=self.prompt, n=1, size="1024x1024"
|
||||||
|
)
|
||||||
|
self.image_url = response.data[0].url
|
||||||
|
self.processing, self.complete = False, True
|
||||||
|
```
|
||||||
|
|
||||||
|
Innerhalb des Zustands definieren wir Funktionen, die als Event-Handler bezeichnet werden und die Zustand-Variablen ändern. Event-Handler sind die Art und Weise, wie wir den Zustand in Reflex ändern können. Sie können als Reaktion auf Benutzeraktionen aufgerufen werden, z.B. beim Klicken auf eine Schaltfläche oder bei der Eingabe in ein Textfeld. Diese Aktionen werden als Ereignisse bezeichnet.
|
||||||
|
|
||||||
|
Unsere DALL-E.-App hat einen Event-Handler, `get_image`, der dieses Bild von der OpenAI-API abruft. Die Verwendung von `yield` in der Mitte eines Event-Handlers führt zu einer Aktualisierung der Benutzeroberfläche. Andernfalls wird die Benutzeroberfläche am Ende des Ereignishandlers aktualisiert.
|
||||||
|
|
||||||
|
### **Routing**
|
||||||
|
|
||||||
|
Schließlich definieren wir unsere App.
|
||||||
|
|
||||||
|
```python
|
||||||
|
app = rx.App()
|
||||||
|
```
|
||||||
|
|
||||||
|
Wir fügen der Indexkomponente eine Seite aus dem Stammverzeichnis der Anwendung hinzu. Wir fügen auch einen Titel hinzu, der in der Seitenvorschau/Browser-Registerkarte angezeigt wird.
|
||||||
|
|
||||||
|
```python
|
||||||
|
app.add_page(index, title="DALL-E")
|
||||||
|
```
|
||||||
|
|
||||||
|
Du kannst eine mehrseitige App erstellen, indem du weitere Seiten hinzufügst.
|
||||||
|
|
||||||
|
## 📑 Ressourcen
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Komponentenbibliothek](https://reflex.dev/docs/library) | 🖼️ [Galerie](https://reflex.dev/docs/gallery) | 🛸 [Bereitstellung](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## ✅ Status
|
||||||
|
|
||||||
|
Reflex wurde im Dezember 2022 unter dem Namen Pynecone gestartet.
|
||||||
|
|
||||||
|
Ab Februar 2024 befindet sich unser Hosting-Service in der Alpha-Phase! In dieser Zeit kann jeder seine Apps kostenlos bereitstellen. Siehe unsere [Roadmap](https://github.com/reflex-dev/reflex/issues/2727), um zu sehen, was geplant ist.
|
||||||
|
|
||||||
|
Reflex hat wöchentliche Veröffentlichungen und neue Features! Stelle sicher, dass du dieses Repository mit einem :star: Stern markierst und :eyes: beobachtest, um auf dem Laufenden zu bleiben.
|
||||||
|
|
||||||
|
## Beitragende
|
||||||
|
|
||||||
|
Wir begrüßen Beiträge jeder Größe! Hier sind einige gute Möglichkeiten, um in der Reflex-Community zu starten.
|
||||||
|
|
||||||
|
- **Tritt unserem Discord bei**: Unser [Discord](https://discord.gg/T5WSbC2YtQ) ist der beste Ort, um Hilfe für dein Reflex-Projekt zu bekommen und zu besprechen, wie du beitragen kannst.
|
||||||
|
- **GitHub-Diskussionen**: Eine großartige Möglichkeit, über Funktionen zu sprechen, die du hinzugefügt haben möchtest oder Dinge, die verwirrend sind/geklärt werden müssen.
|
||||||
|
- **GitHub-Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) sind eine ausgezeichnete Möglichkeit, Bugs zu melden. Außerdem kannst du versuchen, ein bestehendes Problem zu lösen und eine PR einzureichen.
|
||||||
|
|
||||||
|
Wir suchen aktiv nach Mitwirkenden, unabhängig von deinem Erfahrungslevel oder deiner Erfahrung. Um beizutragen, sieh dir [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) an.
|
||||||
|
|
||||||
|
|
||||||
|
## Vielen Dank an unsere Mitwirkenden:
|
||||||
|
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
Reflex ist Open-Source und lizenziert unter der [Apache License 2.0](LICENSE).
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
|
|||||||
|
|
||||||
## ⚙️ Instalación
|
## ⚙️ Instalación
|
||||||
|
|
||||||
Abra un terminal y ejecute (Requiere Python 3.8+):
|
Abra un terminal y ejecute (Requiere Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -239,7 +239,7 @@ Reflex se lanzó en diciembre de 2022 con el nombre de Pynecone.
|
|||||||
- **Discusiones de GitHub**: Una excelente manera de hablar sobre las características que deseas agregar o las cosas que te resultan confusas o necesitan aclaración.
|
- **Discusiones de GitHub**: Una excelente manera de hablar sobre las características que deseas agregar o las cosas que te resultan confusas o necesitan aclaración.
|
||||||
- **GitHub Issues**: Las incidencias son una forma excelente de informar de errores. Además, puedes intentar resolver un problema existente y enviar un PR.
|
- **GitHub Issues**: Las incidencias son una forma excelente de informar de errores. Además, puedes intentar resolver un problema existente y enviar un PR.
|
||||||
|
|
||||||
Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
Buscamos colaboradores, sin importar su nivel o experiencia. Para contribuir consulta [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Licencia
|
## Licencia
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ Pynecone की तलाश हैं? आप सही रेपो में
|
|||||||
### **✨ प्रदर्शनकारी, अनुकूलित वेब ऐप्स, शुद्ध Python में। सेकंडों में तैनात करें। ✨**
|
### **✨ प्रदर्शनकारी, अनुकूलित वेब ऐप्स, शुद्ध Python में। सेकंडों में तैनात करें। ✨**
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
@ -20,7 +19,7 @@ Pynecone की तलाश हैं? आप सही रेपो में
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
## [English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
# Reflex
|
# Reflex
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
|
|||||||
|
|
||||||
## ⚙️ इंस्टॉलेशन (Installation)
|
## ⚙️ इंस्टॉलेशन (Installation)
|
||||||
|
|
||||||
एक टर्मिनल खोलें और चलाएं (Python 3.8+ की आवश्यकता है):
|
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -240,7 +239,7 @@ Reflex में हर सप्ताह नए रिलीज़ और फ
|
|||||||
- **GitHub Discussions** (गिटहब चर्चाएँ): उन सुविधाओं के बारे में बात करने का एक शानदार तरीका जिन्हें आप जोड़ना चाहते हैं या ऐसी चीज़ें जो भ्रमित करने वाली हैं/स्पष्टीकरण की आवश्यकता है।
|
- **GitHub Discussions** (गिटहब चर्चाएँ): उन सुविधाओं के बारे में बात करने का एक शानदार तरीका जिन्हें आप जोड़ना चाहते हैं या ऐसी चीज़ें जो भ्रमित करने वाली हैं/स्पष्टीकरण की आवश्यकता है।
|
||||||
- **GitHub Issues** (गिटहब समस्याएं): ये [बग](https://github.com/reflex-dev/reflex/issues) की रिपोर्ट करने का एक शानदार तरीका है। इसके अतिरिक्त, आप किसी मौजूदा समस्या को हल करने का प्रयास कर सकते हैं और एक पीआर सबमिट कर सकते हैं।
|
- **GitHub Issues** (गिटहब समस्याएं): ये [बग](https://github.com/reflex-dev/reflex/issues) की रिपोर्ट करने का एक शानदार तरीका है। इसके अतिरिक्त, आप किसी मौजूदा समस्या को हल करने का प्रयास कर सकते हैं और एक पीआर सबमिट कर सकते हैं।
|
||||||
|
|
||||||
हम सक्रिय रूप से योगदानकर्ताओं की तलाश कर रहे हैं, चाहे आपका कौशल स्तर या अनुभव कुछ भी हो।योगदान करने के लिए [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) देखें।
|
हम सक्रिय रूप से योगदानकर्ताओं की तलाश कर रहे हैं, चाहे आपका कौशल स्तर या अनुभव कुछ भी हो।योगदान करने के लिए [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) देखें।
|
||||||
|
|
||||||
## हमारे सभी योगदानकर्ताओं का धन्यवाद:
|
## हमारे सभी योगदानकर्ताओं का धन्यवाद:
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
### **✨ App web performanti e personalizzabili in puro Python. Distribuisci in pochi secondi. ✨**
|
### **✨ App web performanti e personalizzabili in puro Python. Distribuisci in pochi secondi. ✨**
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
@ -18,12 +17,12 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) |
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) |
|
||||||
[Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Installazione
|
## ⚙️ Installazione
|
||||||
|
|
||||||
Apri un terminale ed esegui (Richiede Python 3.8+):
|
Apri un terminale ed esegui (Richiede Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -8,34 +8,36 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
### **✨ 即時デプロイが可能な、Pure Pythonで作ったパフォーマンスと汎用性が高いWebアプリケーション✨**
|
### **✨ 即時デプロイが可能な、Pure Python で作ったパフォーマンスと汎用性が高い Web アプリケーション ✨**
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Reflex
|
# Reflex
|
||||||
|
|
||||||
ReflexはPythonのみでフルスタックWebアプリケーションを作成できるライブラリです。
|
Reflex は Python のみでフルスタック Web アプリケーションを作成できるライブラリです。
|
||||||
|
|
||||||
主な特徴:
|
主な特徴:
|
||||||
* **Pure Python** - WebアプリケーションのフロントエンドとバックエンドをPythonのみで実装できるため、Javascriptを学ぶ必要がありません。
|
|
||||||
* **高い柔軟性** - Reflexは簡単に始められて、複雑なアプリケーションまで作成できます。
|
|
||||||
* **即時デプロイ** - ビルド後、すぐにデプロイが可能です。[単純なCLIコマンド](https://reflex.dev/docs/hosting/deploy-quick-start/)を使ったアプリケーションのデプロイや、自身のサーバーへのホストができます。
|
|
||||||
|
|
||||||
Reflexがどのように動作しているかを知るには、[アーキテクチャページ](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)をご覧ください。
|
- **Pure Python** - Web アプリケーションのフロントエンドとバックエンドを Python のみで実装できるため、Javascript を学ぶ必要がありません。
|
||||||
|
- **高い柔軟性** - Reflex は簡単に始められて、複雑なアプリケーションまで作成できます。
|
||||||
|
- **即時デプロイ** - ビルド後、すぐにデプロイが可能です。[単純な CLI コマンド](https://reflex.dev/docs/hosting/deploy-quick-start/)を使ったアプリケーションのデプロイや、自身のサーバーへのホストができます。
|
||||||
|
|
||||||
|
Reflex がどのように動作しているかを知るには、[アーキテクチャページ](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture)をご覧ください。
|
||||||
|
|
||||||
## ⚙️ インストール
|
## ⚙️ インストール
|
||||||
|
|
||||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.8以上が必要です。):
|
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -43,7 +45,7 @@ pip install reflex
|
|||||||
|
|
||||||
## 🥳 最初のアプリケーションを作ろう
|
## 🥳 最初のアプリケーションを作ろう
|
||||||
|
|
||||||
`reflex`をインストールすると、`reflex`のCLIツールが自動でインストールされます。
|
`reflex`をインストールすると、`reflex`の CLI ツールが自動でインストールされます。
|
||||||
|
|
||||||
新しいプロジェクトを作成して、インストールが成功しているかを確認しましょう。(`my_app_name`を自身のプロジェクト名に書き換えて実行ください。):
|
新しいプロジェクトを作成して、インストールが成功しているかを確認しましょう。(`my_app_name`を自身のプロジェクト名に書き換えて実行ください。):
|
||||||
|
|
||||||
@ -63,11 +65,11 @@ reflex run
|
|||||||
|
|
||||||
http://localhost:3000 にアクセスしてアプリの動作を見ることができます。
|
http://localhost:3000 にアクセスしてアプリの動作を見ることができます。
|
||||||
|
|
||||||
`my_app_name/my_app_name.py`のソースコードを編集してみましょう!Reflexはfast refreshなので、ソースを保存した直後に変更がWebページに反映されます。
|
`my_app_name/my_app_name.py`のソースコードを編集してみましょう!Reflex は fast refresh なので、ソースを保存した直後に変更が Web ページに反映されます。
|
||||||
|
|
||||||
## 🫧 実装例
|
## 🫧 実装例
|
||||||
|
|
||||||
実装例を見てみましょう: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node)を中心とした画像生成UIを作成しました。説明を簡単にするためにここでは[OpenAI API](https://platform.openai.com/docs/api-reference/authentication)を呼んでいますが、ローカルで動作している機械学習モデルに置き換えることも可能です。
|
実装例を見てみましょう: [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node)を中心とした画像生成 UI を作成しました。説明を簡単にするためにここでは[OpenAI API](https://platform.openai.com/docs/api-reference/authentication)を呼んでいますが、ローカルで動作している機械学習モデルに置き換えることも可能です。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ http://localhost:3000 にアクセスしてアプリの動作を見ることが
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
画像生成UIのソースコードの全貌を見てみましょう。下記のように、単一のPythonファイルで作れます!
|
画像生成 UI のソースコードの全貌を見てみましょう。下記のように、単一の Python ファイルで作れます!
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
@ -117,14 +119,15 @@ def index():
|
|||||||
on_blur=State.set_prompt,
|
on_blur=State.set_prompt,
|
||||||
width="25em",
|
width="25em",
|
||||||
),
|
),
|
||||||
rx.button("Generate Image", on_click=State.get_image, width="25em"),
|
rx.button(
|
||||||
|
"Generate Image",
|
||||||
|
on_click=State.get_image,
|
||||||
|
width="25em",
|
||||||
|
loading=State.processing
|
||||||
|
),
|
||||||
rx.cond(
|
rx.cond(
|
||||||
State.processing,
|
State.complete,
|
||||||
rx.chakra.circular_progress(is_indeterminate=True),
|
rx.image(src=State.image_url, width="20em"),
|
||||||
rx.cond(
|
|
||||||
State.complete,
|
|
||||||
rx.image(src=State.image_url, width="20em"),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
align="center",
|
align="center",
|
||||||
),
|
),
|
||||||
@ -137,17 +140,15 @@ app = rx.App()
|
|||||||
app.add_page(index, title="Reflex:DALL-E")
|
app.add_page(index, title="Reflex:DALL-E")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## それぞれの実装を見てみましょう
|
## それぞれの実装を見てみましょう
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="../../docs/images/dalle_colored_code_example.png" alt="DALL-E appのフロントエンドとバックエンドのパーツの違いを説明しています。" width="900" />
|
<img src="../../docs/images/dalle_colored_code_example.png" alt="DALL-E appのフロントエンドとバックエンドのパーツの違いを説明しています。" width="900" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### **Reflex UI**
|
### **Reflex UI**
|
||||||
|
|
||||||
UIから見てみましょう。
|
UI から見てみましょう。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def index():
|
def index():
|
||||||
@ -158,13 +159,13 @@ def index():
|
|||||||
|
|
||||||
`index`関数において、アプリのフロントエンドを定義しています。
|
`index`関数において、アプリのフロントエンドを定義しています。
|
||||||
|
|
||||||
フロントエンドを実装するにあたり、`center`、`vstack`、`input`、`button`など異なるコンポーネントを使用しています。コンポーネントはお互いにネストが可能であり、複雑なレイアウトを作成できます。また、keyword argsを使うことで、CSSの機能をすべて使ったスタイルが可能です。
|
フロントエンドを実装するにあたり、`center`、`vstack`、`input`、`button`など異なるコンポーネントを使用しています。コンポーネントはお互いにネストが可能であり、複雑なレイアウトを作成できます。また、keyword args を使うことで、CSS の機能をすべて使ったスタイルが可能です。
|
||||||
|
|
||||||
Reflexは[60を超える内臓コンポーネント](https://reflex.dev/docs/library)があるため、すぐに始められます。私たちは、積極的にコンポーネントを追加していますが、簡単に[自身のコンポーネントを追加](https://reflex.dev/docs/wrapping-react/overview/)することも可能です。
|
Reflex は[60 を超える内臓コンポーネント](https://reflex.dev/docs/library)があるため、すぐに始められます。私たちは、積極的にコンポーネントを追加していますが、簡単に[自身のコンポーネントを追加](https://reflex.dev/docs/wrapping-react/overview/)することも可能です。
|
||||||
|
|
||||||
### **ステート**
|
### **ステート**
|
||||||
|
|
||||||
Reflexはステートの関数を用いてUIを表示します。
|
Reflex はステートの関数を用いて UI を表示します。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
@ -176,9 +177,9 @@ class State(rx.State):
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
ステートでは、アプリで変更が可能な全ての変数(varsと呼びます)と、varsの変更が可能な関数を定義します。
|
ステートでは、アプリで変更が可能な全ての変数(vars と呼びます)と、vars の変更が可能な関数を定義します。
|
||||||
|
|
||||||
この例では、ステートを`prompt`と`image_url`で構成しています。そして、ブール型の`processing`と`complete`を用いて、プログレスサークルと画像の表示を切り替えています。
|
この例では、ステートを`prompt`と`image_url`で構成しています。そして、ブール型の`processing`と`complete`を用いて、ボタンを無効にするタイミング(画像生成中)や生成された画像を表示するタイミングを示しています。
|
||||||
|
|
||||||
### **イベントハンドラ**
|
### **イベントハンドラ**
|
||||||
|
|
||||||
@ -197,9 +198,9 @@ def get_image(self):
|
|||||||
self.processing, self.complete = False, True
|
self.processing, self.complete = False, True
|
||||||
```
|
```
|
||||||
|
|
||||||
ステートにおいて、ステートのvarsを変更できるイベントハンドラ関数を定義しています。イベントハンドラはReflexにおいて、ステートのvarsを変更する方法です。ボタンクリックやテキストボックスの入力など、ユーザのアクションに応じてイベントハンドラが呼ばれます。
|
ステートにおいて、ステートの vars を変更できるイベントハンドラ関数を定義しています。イベントハンドラは Reflex において、ステートの vars を変更する方法です。ボタンクリックやテキストボックスの入力など、ユーザのアクションに応じてイベントハンドラが呼ばれます。
|
||||||
|
|
||||||
DALL·E.アプリには、OpenAI APIからイメージを取得する`get_image`関数があります。イベントハンドラの最後でUIの更新がかかるため、関数の途中に`yield`を入れることで先にUIを更新しています。
|
DALL·E.アプリには、OpenAI API からイメージを取得する`get_image`関数があります。イベントハンドラの最後で UI の更新がかかるため、関数の途中に`yield`を入れることで先に UI を更新しています。
|
||||||
|
|
||||||
### **ルーティング**
|
### **ルーティング**
|
||||||
|
|
||||||
@ -209,7 +210,7 @@ DALL·E.アプリには、OpenAI APIからイメージを取得する`get_image`
|
|||||||
app = rx.App()
|
app = rx.App()
|
||||||
```
|
```
|
||||||
|
|
||||||
アプリにページを追加し、ドキュメントルートをindexコンポーネントにルーティングしています。更に、ページのプレビューやブラウザタブに表示されるタイトルを記載しています。
|
アプリにページを追加し、ドキュメントルートを index コンポーネントにルーティングしています。更に、ページのプレビューやブラウザタブに表示されるタイトルを記載しています。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
app.add_page(index, title="DALL-E")
|
app.add_page(index, title="DALL-E")
|
||||||
@ -221,35 +222,34 @@ app.add_page(index, title="DALL-E")
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## ✅ ステータス
|
## ✅ ステータス
|
||||||
|
|
||||||
2022年12月に、ReflexはPyneconeという名前でローンチしました。
|
2022 年 12 月に、Reflex は Pynecone という名前でローンチしました。
|
||||||
|
|
||||||
2024年2月に、ホスティングサービスをアルファ版でリリースしました!アルファ版では、だれでもReflexアプリケーションを無料でデプロイできます。今後の予定は[ロードマップ](https://github.com/reflex-dev/reflex/issues/2727)において見れます。
|
2024 年 2 月に、ホスティングサービスをアルファ版でリリースしました!アルファ版では、だれでも Reflex アプリケーションを無料でデプロイできます。今後の予定は[ロードマップ](https://github.com/reflex-dev/reflex/issues/2727)において見れます。
|
||||||
|
|
||||||
Reflexは毎週、新しいリリースや機能追加を行っています!最新情報を逃さないために、 :star: Starや :eyes: Watchをお願いします。
|
Reflex は毎週、新しいリリースや機能追加を行っています!最新情報を逃さないために、 :star: Star や :eyes: Watch をお願いします。
|
||||||
|
|
||||||
## コントリビュート
|
## コントリビュート
|
||||||
|
|
||||||
様々なサイズのコントリビュートを歓迎しています!Reflexコミュニティに入るための方法を、いくつかリストアップします。
|
様々なサイズのコントリビュートを歓迎しています!Reflex コミュニティに入るための方法を、いくつかリストアップします。
|
||||||
|
|
||||||
- **Discordに参加**: [Discord](https://discord.gg/T5WSbC2YtQ)は、Reflexプロジェクトの相談や、コントリビュートについての話し合いをするための、最適な場所です。
|
- **Discord に参加**: [Discord](https://discord.gg/T5WSbC2YtQ)は、Reflex プロジェクトの相談や、コントリビュートについての話し合いをするための、最適な場所です。
|
||||||
- **GitHub Discussions**: GitHub Discussionsでは、追加したい機能や、複雑で解明が必要な事柄についての議論に適している場所です。
|
- **GitHub Discussions**: GitHub Discussions では、追加したい機能や、複雑で解明が必要な事柄についての議論に適している場所です。
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)はバグの報告に適している場所です。また、課題を解決したPRのサブミットにチャレンジしていただくことも、可能です。
|
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues)はバグの報告に適している場所です。また、課題を解決した PR のサブミットにチャレンジしていただくことも、可能です。
|
||||||
|
|
||||||
スキルや経験に関わらず、私たちはコントリビュータを積極的に探しています。コントリビュートするために、[CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)をご覧ください。
|
|
||||||
|
|
||||||
|
CONTスキルや経験に関わらず、私たちはコントリビュータを積極的に探しています。コントリビュートするために、[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)をご覧ください。
|
||||||
|
|
||||||
## 私たちのコントリビュータに感謝!:
|
## 私たちのコントリビュータに感謝!:
|
||||||
|
|
||||||
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## ライセンス
|
## ライセンス
|
||||||
|
|
||||||
Reflexはオープンソースであり、[Apache License 2.0](LICENSE)に基づいてライセンス供与されます。
|
Reflex はオープンソースであり、[Apache License 2.0](LICENSE)に基づいてライセンス供与されます。
|
||||||
|
@ -10,18 +10,17 @@
|
|||||||
|
|
||||||
### **✨ 순수 Python으로 고성능 사용자 정의 웹앱을 만들어 보세요. 몇 초만에 배포 가능합니다. ✨**
|
### **✨ 순수 Python으로 고성능 사용자 정의 웹앱을 만들어 보세요. 몇 초만에 배포 가능합니다. ✨**
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
---
|
---
|
||||||
## ⚙️ 설치
|
## ⚙️ 설치
|
||||||
|
|
||||||
터미널을 열고 실행하세요. (Python 3.8+ 필요):
|
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
262
docs/pe/README.md
Normal file
262
docs/pe/README.md
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
```diff
|
||||||
|
+ دنبال Pynecone میگردی؟ شما در مخزن درستی قرار داری. Pynecone به Reflex تغییر نام داده است. +
|
||||||
|
```
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### **✨ برنامه های تحت وب قابل تنظیم، کارآمد تماما پایتونی که در چند ثانیه مستقر(دپلوی) میشود. ✨**
|
||||||
|
[](https://badge.fury.io/py/reflex)
|
||||||
|

|
||||||
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Reflex - رفلکس
|
||||||
|
|
||||||
|
رفلکس(Reflex) یک کتابخانه برای ساخت برنامه های وب فول استک تماما پایتونی است.
|
||||||
|
|
||||||
|
ویژگی های کلیدی:
|
||||||
|
* **تماما پایتونی** - فرانت اند و بک اند برنامه خود را همه در پایتون بنویسید، بدون نیاز به یادگیری جاوا اسکریپت.
|
||||||
|
* **انعطاف پذیری کامل** - شروع به کار با Reflex آسان است، اما می تواند به برنامه های پیچیده نیز تبدیل شود.
|
||||||
|
* **دپلوی فوری** - پس از ساخت، برنامه خود را با [یک دستور](https://reflex.dev/docs/hosting/deploy-quick-start/) دپلوی کنید یا آن را روی سرور خود میزبانی کنید.
|
||||||
|
|
||||||
|
برای آشنایی با نحوه عملکرد Reflex [صفحه معماری](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) را ببینید.
|
||||||
|
|
||||||
|
## ⚙️ Installation - نصب و راه اندازی
|
||||||
|
|
||||||
|
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install reflex
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🥳 اولین برنامه خود را ایجاد کنید
|
||||||
|
|
||||||
|
نصب `reflex` همچنین `reflex` در خط فرمان را نصب میکند.
|
||||||
|
|
||||||
|
با ایجاد یک پروژه جدید موفقیت آمیز بودن نصب را تست کنید. (`my_app_name` را با اسم پروژه خودتان جایگزین کنید):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir my_app_name
|
||||||
|
cd my_app_name
|
||||||
|
reflex init
|
||||||
|
```
|
||||||
|
|
||||||
|
این دستور یک برنامه الگو(تمپلیت) را در فهرست(دایرکتوری) جدید شما مقداردهی اولیه می کند
|
||||||
|
|
||||||
|
می توانید این برنامه را در حالت توسعه(development) اجرا کنید:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
reflex run
|
||||||
|
```
|
||||||
|
|
||||||
|
باید برنامه خود را در حال اجرا ببینید در http://localhost:3000.
|
||||||
|
|
||||||
|
اکنون میتوانید کد منبع را در «my_app_name/my_app_name.py» تغییر دهید. Reflex دارای تازهسازیهای سریعی است، بنابراین میتوانید تغییرات خود را بلافاصله پس از ذخیره کد خود مشاهده کنید.
|
||||||
|
|
||||||
|
|
||||||
|
## 🫧 Example App - برنامه نمونه
|
||||||
|
|
||||||
|
بیایید یک مثال بزنیم: ایجاد یک رابط کاربری برای تولید تصویر [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). برای سادگی، ما فراخوانی میکنیم [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), اما می توانید آن را با یک مدل ML که به صورت محلی اجرا می شود جایگزین کنید.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
در اینجا کد کامل برای ایجاد این پروژه آمده است. همه اینها در یک فایل پایتون انجام می شود!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
import reflex as rx
|
||||||
|
import openai
|
||||||
|
|
||||||
|
openai_client = openai.OpenAI()
|
||||||
|
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
"""The app state."""
|
||||||
|
|
||||||
|
prompt = ""
|
||||||
|
image_url = ""
|
||||||
|
processing = False
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
def get_image(self):
|
||||||
|
"""Get the image from the prompt."""
|
||||||
|
if self.prompt == "":
|
||||||
|
return rx.window_alert("Prompt Empty")
|
||||||
|
|
||||||
|
self.processing, self.complete = True, False
|
||||||
|
yield
|
||||||
|
response = openai_client.images.generate(
|
||||||
|
prompt=self.prompt, n=1, size="1024x1024"
|
||||||
|
)
|
||||||
|
self.image_url = response.data[0].url
|
||||||
|
self.processing, self.complete = False, True
|
||||||
|
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.center(
|
||||||
|
rx.vstack(
|
||||||
|
rx.heading("DALL-E", font_size="1.5em"),
|
||||||
|
rx.input(
|
||||||
|
placeholder="Enter a prompt..",
|
||||||
|
on_blur=State.set_prompt,
|
||||||
|
width="25em",
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Generate Image",
|
||||||
|
on_click=State.get_image,
|
||||||
|
width="25em",
|
||||||
|
loading=State.processing
|
||||||
|
),
|
||||||
|
rx.cond(
|
||||||
|
State.complete,
|
||||||
|
rx.image(src=State.image_url, width="20em"),
|
||||||
|
),
|
||||||
|
align="center",
|
||||||
|
),
|
||||||
|
width="100%",
|
||||||
|
height="100vh",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add state and page to the app.
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index, title="Reflex:DALL-E")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## بیاید سادش کنیم
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="docs/images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
### **Reflex UI - رابط کاربری رفلکس**
|
||||||
|
|
||||||
|
بیایید با رابط کاربری شروع کنیم.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def index():
|
||||||
|
return rx.center(
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
تابع `index` قسمت فرانت اند برنامه را تعریف می کند.
|
||||||
|
|
||||||
|
ما از اجزای مختلفی مثل `center`, `vstack`, `input` و `button` استفاده میکنیم تا فرانت اند را بسازیم. اجزاء را می توان درون یکدیگر قرار داد
|
||||||
|
برای ایجاد طرح بندی های پیچیده می توانید از args کلمات کلیدی برای استایل دادن به آنها از CSS استفاده کنید.
|
||||||
|
|
||||||
|
رفلکس دارای [بیش از 60 جزء](https://reflex.dev/docs/library) برای کمک به شما برای شروع. ما به طور فعال اجزای بیشتری را اضافه می کنیم, و این خیلی آسان است که [اجزا خود را بسازید](https://reflex.dev/docs/wrapping-react/overview/).
|
||||||
|
|
||||||
|
### **State - حالت**
|
||||||
|
|
||||||
|
رفلکس رابط کاربری شما را به عنوان تابعی از وضعیت شما نشان می دهد.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class State(rx.State):
|
||||||
|
"""The app state."""
|
||||||
|
prompt = ""
|
||||||
|
image_url = ""
|
||||||
|
processing = False
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
حالت تمام متغیرها(variables) (به نام vars) را در یک برنامه که می توانند تغییر دهند و توابعی که آنها را تغییر می دهند تعریف می کند..
|
||||||
|
|
||||||
|
در اینجا حالت از یک `prompt` و `image_url` تشکیل شده است. همچنین دو بولی `processing` و `complete` برای نشان دادن زمان غیرفعال کردن دکمه (در طول تولید تصویر) و زمان نمایش تصویر نتیجه وجود دارد.
|
||||||
|
|
||||||
|
### **Event Handlers - کنترل کنندگان رویداد**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_image(self):
|
||||||
|
"""Get the image from the prompt."""
|
||||||
|
if self.prompt == "":
|
||||||
|
return rx.window_alert("Prompt Empty")
|
||||||
|
|
||||||
|
self.processing, self.complete = True, False
|
||||||
|
yield
|
||||||
|
response = openai_client.images.generate(
|
||||||
|
prompt=self.prompt, n=1, size="1024x1024"
|
||||||
|
)
|
||||||
|
self.image_url = response.data[0].url
|
||||||
|
self.processing, self.complete = False, True
|
||||||
|
```
|
||||||
|
|
||||||
|
در داخل حالت، توابعی به نام کنترل کننده رویداد تعریف می کنیم که متغیرهای حالت را تغییر می دهند. کنترل کننده های رویداد راهی هستند که می توانیم وضعیت را در Reflex تغییر دهیم. آنها را می توان در پاسخ به اقدامات کاربر، مانند کلیک کردن روی یک دکمه یا تایپ کردن در یک متن، فراخوانی کرد. به این اعمال وقایع می گویند.
|
||||||
|
|
||||||
|
برنامه DALL·E ما دارای یک کنترل کننده رویداد، `get_image` است که این تصویر را از OpenAI API دریافت می کند. استفاده از `yield` در وسط کنترلکننده رویداد باعث بهروزرسانی رابط کاربری میشود. در غیر این صورت رابط کاربری در پایان کنترل کننده رویداد به روز می شود.
|
||||||
|
|
||||||
|
### **Routing - مسیریابی**
|
||||||
|
|
||||||
|
بالاخره اپلیکیشن خود را تعریف می کنیم.
|
||||||
|
|
||||||
|
```python
|
||||||
|
app = rx.App()
|
||||||
|
```
|
||||||
|
|
||||||
|
ما یک صفحه از root برنامه را به جزء index اضافه می کنیم. ما همچنین عنوانی را اضافه می کنیم که در برگه پیش نمایش/مرورگر صفحه نمایش داده می شود.
|
||||||
|
|
||||||
|
```python
|
||||||
|
app.add_page(index, title="DALL-E")
|
||||||
|
```
|
||||||
|
|
||||||
|
با افزودن صفحات بیشتر می توانید یک برنامه چند صفحه ای ایجاد کنید.
|
||||||
|
|
||||||
|
## 📑 Resources - منابع
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
📑 [اسناد](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [وبلاگ](https://reflex.dev/blog) | 📱 [کتابخانه جزء](https://reflex.dev/docs/library) | 🖼️ [گالری](https://reflex.dev/docs/gallery) | 🛸 [استقرار](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## ✅ Status - وضعیت
|
||||||
|
|
||||||
|
رفلکس(reflex) در دسامبر 2022 با نام Pynecone راه اندازی شد.
|
||||||
|
|
||||||
|
از فوریه 2024، سرویس میزبانی ما در آلفا است! در این مدت هر کسی میتواند برنامههای خود را به صورت رایگان اجرا کند. [نقشه راه](https://github.com/reflex-dev/reflex/issues/2727) را ببینید تا متوجه شوید چه برنامهریزی شده است.
|
||||||
|
|
||||||
|
رفلکس(reflex) هر هفته نسخه ها و ویژگی های جدیدی دارد! مطمئن شوید که :star: ستاره و :eyes: این مخزن را تماشا کنید تا به روز بمانید.
|
||||||
|
|
||||||
|
## Contributing - مشارکت کردن
|
||||||
|
|
||||||
|
ما از مشارکت در هر اندازه استقبال می کنیم! در زیر چند راه خوب برای شروع در انجمن رفلکس آورده شده است.
|
||||||
|
|
||||||
|
- **به Discord ما بپیوندید**: [Discord](https://discord.gg/T5WSbC2YtQ) ما بهترین مکان برای دریافت کمک در مورد پروژه Reflex و بحث در مورد اینکه چگونه می توانید کمک کنید است.
|
||||||
|
- **بحث های GitHub**: راهی عالی برای صحبت در مورد ویژگی هایی که می خواهید اضافه کنید یا چیزهایی که گیج کننده هستند/نیاز به توضیح دارند.
|
||||||
|
- **قسمت مشکلات GitHub**: [قسمت مشکلات](https://github.com/reflex-dev/reflex/issues) یک راه عالی برای گزارش اشکال هستند. علاوه بر این، می توانید یک مشکل موجود را حل کنید و یک PR(pull request) ارسال کنید.
|
||||||
|
|
||||||
|
ما فعالانه به دنبال مشارکت کنندگان هستیم، فارغ از سطح مهارت یا تجربه شما. برای مشارکت [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) را بررسی کنید.
|
||||||
|
|
||||||
|
|
||||||
|
## All Thanks To Our Contributors - با تشکر از همکاران ما:
|
||||||
|
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## License - مجوز
|
||||||
|
|
||||||
|
رفلکس متن باز و تحت مجوز [Apache License 2.0](LICENSE) است.
|
@ -17,11 +17,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
---
|
---
|
||||||
## ⚙️ Instalação
|
## ⚙️ Instalação
|
||||||
|
|
||||||
Abra um terminal e execute (Requer Python 3.8+):
|
Abra um terminal e execute (Requer Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
### **✨ Saf Python'da performanslı, özelleştirilebilir web uygulamaları. Saniyeler içinde dağıtın. ✨**
|
### **✨ Saf Python'da performanslı, özelleştirilebilir web uygulamaları. Saniyeler içinde dağıtın. ✨**
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
@ -19,13 +18,13 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Kurulum
|
## ⚙️ Kurulum
|
||||||
|
|
||||||
Bir terminal açın ve çalıştırın (Python 3.8+ gerekir):
|
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -201,7 +200,7 @@ Daha fazla sayfa ekleyerek çok sayfalı bir uygulama oluşturabilirsiniz.
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -230,7 +229,7 @@ Her boyuttaki katkıları memnuniyetle karşılıyoruz! Aşağıda Reflex toplul
|
|||||||
- **GitHub Discussions**: Eklemek istediğiniz özellikler veya kafa karıştırıcı, açıklığa kavuşturulması gereken şeyler hakkında konuşmanın harika bir yolu.
|
- **GitHub Discussions**: Eklemek istediğiniz özellikler veya kafa karıştırıcı, açıklığa kavuşturulması gereken şeyler hakkında konuşmanın harika bir yolu.
|
||||||
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) hataları bildirmenin mükemmel bir yoludur. Ayrıca mevcut bir sorunu deneyip çözebilir ve bir PR (Pull Requests) gönderebilirsiniz.
|
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) hataları bildirmenin mükemmel bir yoludur. Ayrıca mevcut bir sorunu deneyip çözebilir ve bir PR (Pull Requests) gönderebilirsiniz.
|
||||||
|
|
||||||
Beceri düzeyiniz veya deneyiminiz ne olursa olsun aktif olarak katkıda bulunacak kişiler arıyoruz. Katkı sağlamak için katkı sağlama rehberimize bakabilirsiniz: [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
Beceri düzeyiniz veya deneyiminiz ne olursa olsun aktif olarak katkıda bulunacak kişiler arıyoruz. Katkı sağlamak için katkı sağlama rehberimize bakabilirsiniz: [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Hepsi Katkıda Bulunanlar Sayesinde:
|
## Hepsi Katkıda Bulunanlar Sayesinde:
|
||||||
|
|
||||||
|
267
docs/vi/README.md
Normal file
267
docs/vi/README.md
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
```diff
|
||||||
|
+ Bạn đang tìm kiếm Pynecone? Bạn đã tìm đúng. Pynecone đã được đổi tên thành Reflex. +
|
||||||
|
```
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_dark.svg#gh-light-mode-only" alt="Reflex Logo" width="300px">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/reflex_light.svg#gh-dark-mode-only" alt="Reflex Logo" width="300px">
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### **✨ Ứng dụng web hiệu suất cao, tùy chỉnh bằng Python thuần. Deploy trong vài giây. ✨**
|
||||||
|
[](https://badge.fury.io/py/reflex)
|
||||||
|

|
||||||
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md) | [Tiếng Việt](https://github.com/reflex-dev/reflex/blob/main/docs/vi/README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Reflex
|
||||||
|
|
||||||
|
Reflex là một thư viện để xây dựng ứng dụng web toàn bộ bằng Python thuần.
|
||||||
|
|
||||||
|
Các tính năng chính:
|
||||||
|
* **Python thuần tuý** - Viết toàn bộ ứng dụng cả backend và frontend hoàn toàn bằng Python, không cần học JavaScript.
|
||||||
|
* **Full Flexibility** - Reflex dễ dàng để bắt đầu, nhưng cũng có thể mở rộng lên các ứng dụng phức tạp.
|
||||||
|
* **Deploy Instantly** - Sau khi xây dựng ứng dụng, bạn có thể triển khai bằng [một dòng lệnh](https://reflex.dev/docs/hosting/deploy-quick-start/) hoặc triển khai trên server của riêng bạn.
|
||||||
|
|
||||||
|
Đọc [bài viết về kiến trúc hệ thống](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) để hiểu rõ các hoạt động của Reflex.
|
||||||
|
|
||||||
|
## ⚙️ Cài đặt
|
||||||
|
|
||||||
|
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install reflex
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🥳 Tạo ứng dụng đầu tiên
|
||||||
|
|
||||||
|
Cài đặt `reflex` cũng như cài đặt công cụ dòng lệnh `reflex`.
|
||||||
|
|
||||||
|
Kiểm tra việc cài đặt đã thành công hay chưa bằng cách tạo mới một ứng dụng. (Thay `my_app_name` bằng tên ứng dụng của bạn):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir my_app_name
|
||||||
|
cd my_app_name
|
||||||
|
reflex init
|
||||||
|
```
|
||||||
|
|
||||||
|
Lệnh này tạo ra một ứng dụng mẫu trong một thư mục mới.
|
||||||
|
|
||||||
|
Bạn có thể chạy ứng dụng ở chế độ phát triển.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
reflex run
|
||||||
|
```
|
||||||
|
|
||||||
|
Bạn có thể xem ứng dụng của bạn ở địa chỉ http://localhost:3000.
|
||||||
|
|
||||||
|
Bạn có thể thay đổi mã nguồn ở `my_app_name/my_app_name.py`. Reflex nhanh chóng làm mới và bạn có thể thấy thay đổi trên ứng dụng của bạn ngay lập tức khi bạn lưu file.
|
||||||
|
|
||||||
|
|
||||||
|
## 🫧 Ứng dụng ví dụ
|
||||||
|
|
||||||
|
Bắt đầu với ví dụ: tạo một ứng dụng tạo ảnh bằng [DALL·E](https://platform.openai.com/docs/guides/images/image-generation?context=node). Để cho đơn giản, chúng ta sẽ sử dụng [OpenAI API](https://platform.openai.com/docs/api-reference/authentication), nhưng bạn có thể sử dụng model của chính bạn được triển khai trên local.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/reflex-dev/reflex/main/docs/images/dalle.gif" alt="A frontend wrapper for DALL·E, shown in the process of generating an image." width="550" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Đây là toàn bộ đoạn mã để xây dựng ứng dụng trên. Nó được viết hoàn toàn trong một file Python!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
import reflex as rx
|
||||||
|
import openai
|
||||||
|
|
||||||
|
openai_client = openai.OpenAI()
|
||||||
|
|
||||||
|
|
||||||
|
class State(rx.State):
|
||||||
|
"""The app state."""
|
||||||
|
|
||||||
|
prompt = ""
|
||||||
|
image_url = ""
|
||||||
|
processing = False
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
def get_image(self):
|
||||||
|
"""Get the image from the prompt."""
|
||||||
|
if self.prompt == "":
|
||||||
|
return rx.window_alert("Prompt Empty")
|
||||||
|
|
||||||
|
self.processing, self.complete = True, False
|
||||||
|
yield
|
||||||
|
response = openai_client.images.generate(
|
||||||
|
prompt=self.prompt, n=1, size="1024x1024"
|
||||||
|
)
|
||||||
|
self.image_url = response.data[0].url
|
||||||
|
self.processing, self.complete = False, True
|
||||||
|
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return rx.center(
|
||||||
|
rx.vstack(
|
||||||
|
rx.heading("DALL-E", font_size="1.5em"),
|
||||||
|
rx.input(
|
||||||
|
placeholder="Enter a prompt..",
|
||||||
|
on_blur=State.set_prompt,
|
||||||
|
width="25em",
|
||||||
|
),
|
||||||
|
rx.button(
|
||||||
|
"Generate Image",
|
||||||
|
on_click=State.get_image,
|
||||||
|
width="25em",
|
||||||
|
loading=State.processing
|
||||||
|
),
|
||||||
|
rx.cond(
|
||||||
|
State.complete,
|
||||||
|
rx.image(src=State.image_url, width="20em"),
|
||||||
|
),
|
||||||
|
align="center",
|
||||||
|
),
|
||||||
|
width="100%",
|
||||||
|
height="100vh",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add state and page to the app.
|
||||||
|
app = rx.App()
|
||||||
|
app.add_page(index, title="Reflex:DALL-E")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Hãy phân tích chi tiết.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="../images/dalle_colored_code_example.png" alt="Explaining the differences between backend and frontend parts of the DALL-E app." width="900" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
### **Reflex UI**
|
||||||
|
|
||||||
|
Bắt đầu với giao diện chính.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def index():
|
||||||
|
return rx.center(
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Hàm `index` định nghĩa phần giao diện chính của ứng dụng.
|
||||||
|
|
||||||
|
Chúng tôi sử dụng các component (thành phần) khác nhau như `center`, `vstack`, `input` và `button` để xây dựng giao diện phía trước.
|
||||||
|
Các component có thể được lồng vào nhau để tạo ra các bố cục phức tạp. Và bạn cũng có thể sử dụng từ khoá `args` để tận dụng đầy đủ sức mạnh của CSS.
|
||||||
|
|
||||||
|
Reflex có đến hơn [60 component được xây dựng sẵn](https://reflex.dev/docs/library) để giúp bạn bắt đầu. Chúng ta có thể tạo ra một component mới khá dễ dàng, thao khảo: [xây dựng component của riêng bạn](https://reflex.dev/docs/wrapping-react/overview/).
|
||||||
|
|
||||||
|
### **State**
|
||||||
|
|
||||||
|
Reflex biểu diễn giao diện bằng các hàm của state (trạng thái).
|
||||||
|
|
||||||
|
```python
|
||||||
|
class State(rx.State):
|
||||||
|
"""The app state."""
|
||||||
|
prompt = ""
|
||||||
|
image_url = ""
|
||||||
|
processing = False
|
||||||
|
complete = False
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Một state định nghĩa các biến (được gọi là vars) có thể thay đổi trong một ứng dụng và cho phép các hàm có thể thay đổi chúng.
|
||||||
|
|
||||||
|
Tại đây state được cấu thành từ một `prompt` và `image_url`.
|
||||||
|
Có cũng những biến boolean `processing` và `complete`
|
||||||
|
để chỉ ra khi nào tắt nút (trong quá trình tạo hình ảnh)
|
||||||
|
và khi nào hiển thị hình ảnh kết quả.
|
||||||
|
|
||||||
|
### **Event Handlers**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_image(self):
|
||||||
|
"""Get the image from the prompt."""
|
||||||
|
if self.prompt == "":
|
||||||
|
return rx.window_alert("Prompt Empty")
|
||||||
|
|
||||||
|
self.processing, self.complete = True, False
|
||||||
|
yield
|
||||||
|
response = openai_client.images.generate(
|
||||||
|
prompt=self.prompt, n=1, size="1024x1024"
|
||||||
|
)
|
||||||
|
self.image_url = response.data[0].url
|
||||||
|
self.processing, self.complete = False, True
|
||||||
|
```
|
||||||
|
|
||||||
|
Với các state, chúng ta định nghĩa các hàm có thể thay đổi state vars được gọi là event handlers. Event handler là cách chúng ta có thể thay đổi state trong Reflex. Chúng có thể là phản hồi khi người dùng thao tác, chằng hạn khi nhấn vào nút hoặc khi đang nhập trong text box. Các hành động này được gọi là event.
|
||||||
|
|
||||||
|
Ứng dụng DALL·E. của chúng ta có một event handler, `get_image` để lấy hình ảnh từ OpenAI API. Sử dụng từ khoá `yield` in ở giữa event handler để cập nhật giao diện. Hoặc giao diện có thể cập nhật ở cuối event handler.
|
||||||
|
|
||||||
|
### **Routing**
|
||||||
|
|
||||||
|
Cuối cùng, chúng ta định nghĩa một ứng dụng.
|
||||||
|
|
||||||
|
```python
|
||||||
|
app = rx.App()
|
||||||
|
```
|
||||||
|
|
||||||
|
Chúng ta thêm một trang ở đầu ứng dụng bằng index component. Chúng ta cũng thêm tiêu đề của ứng dụng để hiển thị lên trình duyệt.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
app.add_page(index, title="DALL-E")
|
||||||
|
```
|
||||||
|
|
||||||
|
Bạn có thể tạo một ứng dụng nhiều trang bằng cách thêm trang.
|
||||||
|
|
||||||
|
## 📑 Tài liệu
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## ✅ Status
|
||||||
|
|
||||||
|
Reflex phát hành vào tháng 12/2022 với tên là Pynecone.
|
||||||
|
|
||||||
|
Đến tháng 02/2024, chúng tôi tạo ra dịch vụ dưới phiên bản alpha! Trong thời gian này mọi người có thể triển khai ứng dụng hoàn toàn miễn phí. Xem [roadmap](https://github.com/reflex-dev/reflex/issues/2727) để biết thêm chi tiết.
|
||||||
|
|
||||||
|
Reflex ra phiên bản mới với các tính năng mới hàng tuần! Hãy :star: star và :eyes: watch repo này để thấy các cập nhật mới nhất.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Chúng tôi chào đón mọi đóng góp dù lớn hay nhỏ. Dưới đây là các cách để bắt đầu với cộng đồng Reflex.
|
||||||
|
|
||||||
|
- **Discord**: [Discord](https://discord.gg/T5WSbC2YtQ) của chúng tôi là nơi tốt nhất để nhờ sự giúp đỡ và thảo luận các bạn có thể đóng góp.
|
||||||
|
- **GitHub Discussions**: Là cách tốt nhất để thảo luận về các tính năng mà bạn có thể đóng góp hoặc những điều bạn chưa rõ.
|
||||||
|
- **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) là nơi tốt nhất để thông báo. Ngoài ra bạn có thể sửa chữa các vấn đề bằng cách tạo PR.
|
||||||
|
|
||||||
|
Chúng tôi luôn sẵn sàng tìm kiếm các contributor, bất kể kinh nghiệm. Để tham gia đóng góp, xin mời xem
|
||||||
|
[CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Xin cảm ơn các Contributors:
|
||||||
|
<a href="https://github.com/reflex-dev/reflex/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=reflex-dev/reflex" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Reflex là mã nguồn mở và sử dụng giấy phép [Apache License 2.0](LICENSE).
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
### **✨ 使用 Python 创建高效且可自定义的网页应用程序,几秒钟内即可部署.✨**
|
### **✨ 使用 Python 创建高效且可自定义的网页应用程序,几秒钟内即可部署.✨**
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
@ -18,7 +17,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
|
|||||||
|
|
||||||
## ⚙️ 安装
|
## ⚙️ 安装
|
||||||
|
|
||||||
打开一个终端并且运行(要求Python3.8+):
|
打开一个终端并且运行(要求Python3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
**✨ 使用 Python 建立高效且可自訂的網頁應用程式,幾秒鐘內即可部署。✨**
|
**✨ 使用 Python 建立高效且可自訂的網頁應用程式,幾秒鐘內即可部署。✨**
|
||||||
|
|
||||||
[](https://badge.fury.io/py/reflex)
|
[](https://badge.fury.io/py/reflex)
|
||||||

|
|
||||||

|

|
||||||
[](https://reflex.dev/docs/getting-started/introduction)
|
[](https://reflex.dev/docs/getting-started/introduction)
|
||||||
[](https://discord.gg/T5WSbC2YtQ)
|
[](https://discord.gg/T5WSbC2YtQ)
|
||||||
@ -20,7 +19,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md)
|
[English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md) | [日本語](https://github.com/reflex-dev/reflex/blob/main/docs/ja/README.md) | [Deutsch](https://github.com/reflex-dev/reflex/blob/main/docs/de/README.md) | [Persian (پارسی)](https://github.com/reflex-dev/reflex/blob/main/docs/pe/README.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
|
|||||||
|
|
||||||
## ⚙️ 安裝
|
## ⚙️ 安裝
|
||||||
|
|
||||||
開啟一個終端機並且執行 (需要 Python 3.8+):
|
開啟一個終端機並且執行 (需要 Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
@ -230,7 +229,7 @@ app.add_page(index, title="DALL-E")
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Gallery](https://reflex.dev/docs/gallery) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
📑 [Docs](https://reflex.dev/docs/getting-started/introduction) | 🗞️ [Blog](https://reflex.dev/blog) | 📱 [Component Library](https://reflex.dev/docs/library) | 🖼️ [Templates](https://reflex.dev/templates/) | 🛸 [Deployment](https://reflex.dev/docs/hosting/deploy-quick-start)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -252,7 +251,7 @@ Reflex 每周都有新功能和釋出新版本! 確保你按下 :star: 和 :eyes
|
|||||||
- **GitHub Discussions**: 這是一個討論您想新增的功能或對於一些困惑/需要澄清事項的好方法。
|
- **GitHub Discussions**: 這是一個討論您想新增的功能或對於一些困惑/需要澄清事項的好方法。
|
||||||
- **GitHub Issues**: 在 [Issues](https://github.com/reflex-dev/reflex/issues) 頁面報告錯誤是一個絕佳的方式。此外,您也可以嘗試解決現有 Issue 並提交 PR。
|
- **GitHub Issues**: 在 [Issues](https://github.com/reflex-dev/reflex/issues) 頁面報告錯誤是一個絕佳的方式。此外,您也可以嘗試解決現有 Issue 並提交 PR。
|
||||||
|
|
||||||
我們積極尋找貢獻者,不論您的技能水平或經驗如何。要貢獻,請查看 [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
我們積極尋找貢獻者,不論您的技能水平或經驗如何。要貢獻,請查看 [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
|
||||||
## 感謝所有貢獻者:
|
## 感謝所有貢獻者:
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
"""Test @rx.background task functionality."""
|
|
||||||
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from selenium.webdriver.common.by import By
|
|
||||||
|
|
||||||
from reflex.testing import DEFAULT_TIMEOUT, AppHarness, WebDriver
|
|
||||||
|
|
||||||
|
|
||||||
def BackgroundTask():
|
|
||||||
"""Test that background tasks work as expected."""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
counter: int = 0
|
|
||||||
_task_id: int = 0
|
|
||||||
iterations: int = 10
|
|
||||||
|
|
||||||
@rx.background
|
|
||||||
async def handle_event(self):
|
|
||||||
async with self:
|
|
||||||
self._task_id += 1
|
|
||||||
for _ix in range(int(self.iterations)):
|
|
||||||
async with self:
|
|
||||||
self.counter += 1
|
|
||||||
await asyncio.sleep(0.005)
|
|
||||||
|
|
||||||
@rx.background
|
|
||||||
async def handle_event_yield_only(self):
|
|
||||||
async with self:
|
|
||||||
self._task_id += 1
|
|
||||||
for ix in range(int(self.iterations)):
|
|
||||||
if ix % 2 == 0:
|
|
||||||
yield State.increment_arbitrary(1) # type: ignore
|
|
||||||
else:
|
|
||||||
yield State.increment() # type: ignore
|
|
||||||
await asyncio.sleep(0.005)
|
|
||||||
|
|
||||||
def increment(self):
|
|
||||||
self.counter += 1
|
|
||||||
|
|
||||||
@rx.background
|
|
||||||
async def increment_arbitrary(self, amount: int):
|
|
||||||
async with self:
|
|
||||||
self.counter += int(amount)
|
|
||||||
|
|
||||||
def reset_counter(self):
|
|
||||||
self.counter = 0
|
|
||||||
|
|
||||||
async def blocking_pause(self):
|
|
||||||
await asyncio.sleep(0.02)
|
|
||||||
|
|
||||||
@rx.background
|
|
||||||
async def non_blocking_pause(self):
|
|
||||||
await asyncio.sleep(0.02)
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
|
||||||
return rx.vstack(
|
|
||||||
rx.chakra.input(
|
|
||||||
id="token", value=State.router.session.client_token, is_read_only=True
|
|
||||||
),
|
|
||||||
rx.heading(State.counter, id="counter"),
|
|
||||||
rx.chakra.input(
|
|
||||||
id="iterations",
|
|
||||||
placeholder="Iterations",
|
|
||||||
value=State.iterations.to_string(), # type: ignore
|
|
||||||
on_change=State.set_iterations, # type: ignore
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Delayed Increment",
|
|
||||||
on_click=State.handle_event,
|
|
||||||
id="delayed-increment",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Yield Increment",
|
|
||||||
on_click=State.handle_event_yield_only,
|
|
||||||
id="yield-increment",
|
|
||||||
),
|
|
||||||
rx.button("Increment 1", on_click=State.increment, id="increment"),
|
|
||||||
rx.button(
|
|
||||||
"Blocking Pause",
|
|
||||||
on_click=State.blocking_pause,
|
|
||||||
id="blocking-pause",
|
|
||||||
),
|
|
||||||
rx.button(
|
|
||||||
"Non-Blocking Pause",
|
|
||||||
on_click=State.non_blocking_pause,
|
|
||||||
id="non-blocking-pause",
|
|
||||||
),
|
|
||||||
rx.button("Reset", on_click=State.reset_counter, id="reset"),
|
|
||||||
)
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
app.add_page(index)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def background_task(
|
|
||||||
tmp_path_factory,
|
|
||||||
) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Start BackgroundTask app at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
running AppHarness instance
|
|
||||||
"""
|
|
||||||
with AppHarness.create(
|
|
||||||
root=tmp_path_factory.mktemp(f"background_task"),
|
|
||||||
app_source=BackgroundTask, # type: ignore
|
|
||||||
) as harness:
|
|
||||||
yield harness
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def driver(background_task: AppHarness) -> Generator[WebDriver, None, None]:
|
|
||||||
"""Get an instance of the browser open to the background_task app.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
background_task: harness for BackgroundTask app
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
WebDriver instance.
|
|
||||||
"""
|
|
||||||
assert background_task.app_instance is not None, "app is not running"
|
|
||||||
driver = background_task.frontend()
|
|
||||||
try:
|
|
||||||
yield driver
|
|
||||||
finally:
|
|
||||||
driver.quit()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def token(background_task: AppHarness, driver: WebDriver) -> str:
|
|
||||||
"""Get a function that returns the active token.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
background_task: harness for BackgroundTask app.
|
|
||||||
driver: WebDriver instance.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The token for the connected client
|
|
||||||
"""
|
|
||||||
assert background_task.app_instance is not None
|
|
||||||
token_input = driver.find_element(By.ID, "token")
|
|
||||||
assert token_input
|
|
||||||
|
|
||||||
# wait for the backend connection to send the token
|
|
||||||
token = background_task.poll_for_value(token_input, timeout=DEFAULT_TIMEOUT * 2)
|
|
||||||
assert token is not None
|
|
||||||
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
def test_background_task(
|
|
||||||
background_task: AppHarness,
|
|
||||||
driver: WebDriver,
|
|
||||||
token: str,
|
|
||||||
):
|
|
||||||
"""Test that background tasks work as expected.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
background_task: harness for BackgroundTask app.
|
|
||||||
driver: WebDriver instance.
|
|
||||||
token: The token for the connected client.
|
|
||||||
"""
|
|
||||||
assert background_task.app_instance is not None
|
|
||||||
|
|
||||||
# get a reference to all buttons
|
|
||||||
delayed_increment_button = driver.find_element(By.ID, "delayed-increment")
|
|
||||||
yield_increment_button = driver.find_element(By.ID, "yield-increment")
|
|
||||||
increment_button = driver.find_element(By.ID, "increment")
|
|
||||||
blocking_pause_button = driver.find_element(By.ID, "blocking-pause")
|
|
||||||
non_blocking_pause_button = driver.find_element(By.ID, "non-blocking-pause")
|
|
||||||
driver.find_element(By.ID, "reset")
|
|
||||||
|
|
||||||
# get a reference to the counter
|
|
||||||
counter = driver.find_element(By.ID, "counter")
|
|
||||||
|
|
||||||
# get a reference to the iterations input
|
|
||||||
iterations_input = driver.find_element(By.ID, "iterations")
|
|
||||||
|
|
||||||
# kick off background tasks
|
|
||||||
iterations_input.clear()
|
|
||||||
iterations_input.send_keys("50")
|
|
||||||
delayed_increment_button.click()
|
|
||||||
blocking_pause_button.click()
|
|
||||||
delayed_increment_button.click()
|
|
||||||
for _ in range(10):
|
|
||||||
increment_button.click()
|
|
||||||
blocking_pause_button.click()
|
|
||||||
delayed_increment_button.click()
|
|
||||||
delayed_increment_button.click()
|
|
||||||
yield_increment_button.click()
|
|
||||||
non_blocking_pause_button.click()
|
|
||||||
yield_increment_button.click()
|
|
||||||
blocking_pause_button.click()
|
|
||||||
yield_increment_button.click()
|
|
||||||
for _ in range(10):
|
|
||||||
increment_button.click()
|
|
||||||
yield_increment_button.click()
|
|
||||||
blocking_pause_button.click()
|
|
||||||
assert background_task._poll_for(lambda: counter.text == "420", timeout=40)
|
|
||||||
# all tasks should have exited and cleaned up
|
|
||||||
assert background_task._poll_for(
|
|
||||||
lambda: not background_task.app_instance.background_tasks # type: ignore
|
|
||||||
)
|
|
@ -1,107 +0,0 @@
|
|||||||
"""Test that per-component state scaffold works and operates independently."""
|
|
||||||
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from selenium.webdriver.common.by import By
|
|
||||||
|
|
||||||
from reflex.testing import AppHarness
|
|
||||||
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
|
|
||||||
def ComponentStateApp():
|
|
||||||
"""App using per component state."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
class MultiCounter(rx.ComponentState):
|
|
||||||
count: int = 0
|
|
||||||
|
|
||||||
def increment(self):
|
|
||||||
self.count += 1
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_component(cls, *children, **props):
|
|
||||||
return rx.vstack(
|
|
||||||
*children,
|
|
||||||
rx.heading(cls.count, id=f"count-{props.get('id', 'default')}"),
|
|
||||||
rx.button(
|
|
||||||
"Increment",
|
|
||||||
on_click=cls.increment,
|
|
||||||
id=f"button-{props.get('id', 'default')}",
|
|
||||||
),
|
|
||||||
**props,
|
|
||||||
)
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State) # noqa
|
|
||||||
|
|
||||||
@rx.page()
|
|
||||||
def index():
|
|
||||||
mc_a = MultiCounter.create(id="a")
|
|
||||||
mc_b = MultiCounter.create(id="b")
|
|
||||||
assert mc_a.State != mc_b.State
|
|
||||||
return rx.vstack(
|
|
||||||
mc_a,
|
|
||||||
mc_b,
|
|
||||||
rx.button(
|
|
||||||
"Inc A",
|
|
||||||
on_click=mc_a.State.increment, # type: ignore
|
|
||||||
id="inc-a",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def component_state_app(tmp_path) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Start ComponentStateApp app at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path: pytest tmp_path fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
running AppHarness instance
|
|
||||||
"""
|
|
||||||
with AppHarness.create(
|
|
||||||
root=tmp_path,
|
|
||||||
app_source=ComponentStateApp, # type: ignore
|
|
||||||
) as harness:
|
|
||||||
yield harness
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_component_state_app(component_state_app: AppHarness):
|
|
||||||
"""Increment counters independently.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
component_state_app: harness for ComponentStateApp app
|
|
||||||
"""
|
|
||||||
assert component_state_app.app_instance is not None, "app is not running"
|
|
||||||
driver = component_state_app.frontend()
|
|
||||||
|
|
||||||
ss = utils.SessionStorage(driver)
|
|
||||||
assert AppHarness._poll_for(lambda: ss.get("token") is not None), "token not found"
|
|
||||||
|
|
||||||
count_a = driver.find_element(By.ID, "count-a")
|
|
||||||
count_b = driver.find_element(By.ID, "count-b")
|
|
||||||
button_a = driver.find_element(By.ID, "button-a")
|
|
||||||
button_b = driver.find_element(By.ID, "button-b")
|
|
||||||
button_inc_a = driver.find_element(By.ID, "inc-a")
|
|
||||||
|
|
||||||
assert count_a.text == "0"
|
|
||||||
|
|
||||||
button_a.click()
|
|
||||||
assert component_state_app.poll_for_content(count_a, exp_not_equal="0") == "1"
|
|
||||||
|
|
||||||
button_a.click()
|
|
||||||
assert component_state_app.poll_for_content(count_a, exp_not_equal="1") == "2"
|
|
||||||
|
|
||||||
button_inc_a.click()
|
|
||||||
assert component_state_app.poll_for_content(count_a, exp_not_equal="2") == "3"
|
|
||||||
|
|
||||||
assert count_b.text == "0"
|
|
||||||
|
|
||||||
button_b.click()
|
|
||||||
assert component_state_app.poll_for_content(count_b, exp_not_equal="0") == "1"
|
|
||||||
|
|
||||||
button_b.click()
|
|
||||||
assert component_state_app.poll_for_content(count_b, exp_not_equal="1") == "2"
|
|
@ -1,170 +0,0 @@
|
|||||||
"""Integration tests for table and related components."""
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from selenium.webdriver.common.by import By
|
|
||||||
|
|
||||||
from reflex.testing import AppHarness
|
|
||||||
|
|
||||||
|
|
||||||
def Table():
|
|
||||||
"""App using table component."""
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
class TableState(rx.State):
|
|
||||||
rows: List[List[str]] = [
|
|
||||||
["John", "30", "New York"],
|
|
||||||
["Jane", "31", "San Fransisco"],
|
|
||||||
["Joe", "32", "Los Angeles"],
|
|
||||||
]
|
|
||||||
|
|
||||||
headers: List[str] = ["Name", "Age", "Location"]
|
|
||||||
|
|
||||||
footers: List[str] = ["footer1", "footer2", "footer3"]
|
|
||||||
|
|
||||||
caption: str = "random caption"
|
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
|
||||||
|
|
||||||
@app.add_page
|
|
||||||
def index():
|
|
||||||
return rx.center(
|
|
||||||
rx.chakra.input(
|
|
||||||
id="token",
|
|
||||||
value=TableState.router.session.client_token,
|
|
||||||
is_read_only=True,
|
|
||||||
),
|
|
||||||
rx.chakra.table_container(
|
|
||||||
rx.chakra.table(
|
|
||||||
headers=TableState.headers,
|
|
||||||
rows=TableState.rows,
|
|
||||||
footers=TableState.footers,
|
|
||||||
caption=TableState.caption,
|
|
||||||
variant="striped",
|
|
||||||
color_scheme="blue",
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.add_page
|
|
||||||
def another():
|
|
||||||
return rx.center(
|
|
||||||
rx.chakra.table_container(
|
|
||||||
rx.chakra.table( # type: ignore
|
|
||||||
rx.chakra.thead( # type: ignore
|
|
||||||
rx.chakra.tr( # type: ignore
|
|
||||||
rx.chakra.th("Name"),
|
|
||||||
rx.chakra.th("Age"),
|
|
||||||
rx.chakra.th("Location"),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
rx.chakra.tbody( # type: ignore
|
|
||||||
rx.chakra.tr( # type: ignore
|
|
||||||
rx.chakra.td("John"),
|
|
||||||
rx.chakra.td(30),
|
|
||||||
rx.chakra.td("New York"),
|
|
||||||
),
|
|
||||||
rx.chakra.tr( # type: ignore
|
|
||||||
rx.chakra.td("Jane"),
|
|
||||||
rx.chakra.td(31),
|
|
||||||
rx.chakra.td("San Francisco"),
|
|
||||||
),
|
|
||||||
rx.chakra.tr( # type: ignore
|
|
||||||
rx.chakra.td("Joe"),
|
|
||||||
rx.chakra.td(32),
|
|
||||||
rx.chakra.td("Los Angeles"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.tfoot( # type: ignore
|
|
||||||
rx.chakra.tr(
|
|
||||||
rx.chakra.td("footer1"),
|
|
||||||
rx.chakra.td("footer2"),
|
|
||||||
rx.chakra.td("footer3"),
|
|
||||||
) # type: ignore
|
|
||||||
),
|
|
||||||
rx.chakra.table_caption("random caption"),
|
|
||||||
variant="striped",
|
|
||||||
color_scheme="teal",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def table(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
|
||||||
"""Start Table app at tmp_path via AppHarness.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tmp_path_factory: pytest tmp_path_factory fixture
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
running AppHarness instance
|
|
||||||
|
|
||||||
"""
|
|
||||||
with AppHarness.create(
|
|
||||||
root=tmp_path_factory.mktemp("table"),
|
|
||||||
app_source=Table, # type: ignore
|
|
||||||
) as harness:
|
|
||||||
assert harness.app_instance is not None, "app is not running"
|
|
||||||
yield harness
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def driver(table: AppHarness):
|
|
||||||
"""GEt an instance of the browser open to the table app.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
table: harness for Table app
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
WebDriver instance.
|
|
||||||
"""
|
|
||||||
driver = table.frontend()
|
|
||||||
try:
|
|
||||||
token_input = driver.find_element(By.ID, "token")
|
|
||||||
assert token_input
|
|
||||||
# wait for the backend connection to send the token
|
|
||||||
token = table.poll_for_value(token_input)
|
|
||||||
assert token is not None
|
|
||||||
|
|
||||||
yield driver
|
|
||||||
finally:
|
|
||||||
driver.quit()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("route", ["", "/another"])
|
|
||||||
def test_table(driver, table: AppHarness, route):
|
|
||||||
"""Test that a table component is rendered properly.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
driver: Selenium WebDriver open to the app
|
|
||||||
table: Harness for Table app
|
|
||||||
route: Page route or path.
|
|
||||||
"""
|
|
||||||
driver.get(f"{table.frontend_url}/{route}")
|
|
||||||
assert table.app_instance is not None, "app is not running"
|
|
||||||
|
|
||||||
thead = driver.find_element(By.TAG_NAME, "thead")
|
|
||||||
# poll till page is fully loaded.
|
|
||||||
table.poll_for_content(element=thead)
|
|
||||||
# check headers
|
|
||||||
assert thead.find_element(By.TAG_NAME, "tr").text == "NAME AGE LOCATION"
|
|
||||||
# check first row value
|
|
||||||
assert (
|
|
||||||
driver.find_element(By.TAG_NAME, "tbody")
|
|
||||||
.find_elements(By.TAG_NAME, "tr")[0]
|
|
||||||
.text
|
|
||||||
== "John 30 New York"
|
|
||||||
)
|
|
||||||
# check footer
|
|
||||||
assert (
|
|
||||||
driver.find_element(By.TAG_NAME, "tfoot")
|
|
||||||
.find_element(By.TAG_NAME, "tr")
|
|
||||||
.text.lower()
|
|
||||||
== "footer1 footer2 footer3"
|
|
||||||
)
|
|
||||||
# check caption
|
|
||||||
assert driver.find_element(By.TAG_NAME, "caption").text == "random caption"
|
|
@ -1,67 +0,0 @@
|
|||||||
"""Integration tests for all urls in Reflex."""
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
def check_urls(repo_dir):
|
|
||||||
"""Check that all URLs in the repo are valid and secure.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
repo_dir: The directory of the repo.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A list of errors.
|
|
||||||
"""
|
|
||||||
url_pattern = re.compile(r'http[s]?://reflex\.dev[^\s")]*')
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
for root, _dirs, files in os.walk(repo_dir):
|
|
||||||
if "__pycache__" in root:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for file_name in files:
|
|
||||||
if not file_name.endswith(".py") and not file_name.endswith(".md"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
file_path = os.path.join(root, file_name)
|
|
||||||
try:
|
|
||||||
with open(file_path, "r", encoding="utf-8", errors="ignore") as file:
|
|
||||||
for line in file:
|
|
||||||
urls = url_pattern.findall(line)
|
|
||||||
for url in set(urls):
|
|
||||||
if url.startswith("http://"):
|
|
||||||
errors.append(
|
|
||||||
f"Found insecure HTTP URL: {url} in {file_path}"
|
|
||||||
)
|
|
||||||
url = url.strip('"\n')
|
|
||||||
try:
|
|
||||||
response = requests.head(
|
|
||||||
url, allow_redirects=True, timeout=5
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.RequestException as e:
|
|
||||||
errors.append(
|
|
||||||
f"Error accessing URL: {url} in {file_path} | Error: {e}, , Check your path ends with a /"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(f"Error reading file: {file_path} | Error: {e}")
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"repo_dir",
|
|
||||||
[Path(__file__).resolve().parent.parent / "reflex"],
|
|
||||||
)
|
|
||||||
def test_find_and_check_urls(repo_dir):
|
|
||||||
"""Test that all URLs in the repo are valid and secure.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
repo_dir: The directory of the repo.
|
|
||||||
"""
|
|
||||||
errors = check_urls(repo_dir)
|
|
||||||
assert not errors, "\n".join(errors)
|
|
3439
poetry.lock
generated
3439
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
143
pyproject.toml
143
pyproject.toml
@ -1,92 +1,74 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "reflex"
|
name = "reflex"
|
||||||
version = "0.5.3"
|
version = "0.7.2dev1"
|
||||||
description = "Web apps in pure Python."
|
description = "Web apps in pure Python."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikhil Rao <nikhil@reflex.dev>",
|
"Nikhil Rao <nikhil@reflex.dev>",
|
||||||
"Alek Petuskey <alek@reflex.dev>",
|
"Alek Petuskey <alek@reflex.dev>",
|
||||||
"Masen Furer <masen@reflex.dev>",
|
"Masen Furer <masen@reflex.dev>",
|
||||||
"Elijah Ahianyo <elijah@reflex.dev>",
|
"Elijah Ahianyo <elijah@reflex.dev>",
|
||||||
"Thomas Brandého <thomas@reflex.dev>",
|
"Thomas Brandého <thomas@reflex.dev>",
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://reflex.dev"
|
homepage = "https://reflex.dev"
|
||||||
repository = "https://github.com/reflex-dev/reflex"
|
repository = "https://github.com/reflex-dev/reflex"
|
||||||
documentation = "https://reflex.dev/docs/getting-started/introduction"
|
documentation = "https://reflex.dev/docs/getting-started/introduction"
|
||||||
keywords = [
|
keywords = ["web", "framework"]
|
||||||
"web",
|
classifiers = ["Development Status :: 4 - Beta"]
|
||||||
"framework",
|
|
||||||
]
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 4 - Beta",
|
|
||||||
]
|
|
||||||
packages = [
|
|
||||||
{include = "reflex"}
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = ">=3.10, <4.0"
|
||||||
dill = ">=0.3.8,<0.4"
|
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
||||||
fastapi = ">=0.96.0,<1.0"
|
gunicorn = ">=20.1.0,<24.0"
|
||||||
gunicorn = ">=20.1.0,<23.0"
|
|
||||||
jinja2 = ">=3.1.2,<4.0"
|
jinja2 = ">=3.1.2,<4.0"
|
||||||
psutil = ">=5.9.4,<6.0"
|
psutil = ">=5.9.4,<7.0"
|
||||||
pydantic = ">=1.10.2,<3.0"
|
pydantic = ">=1.10.21,<3.0"
|
||||||
python-multipart = ">=0.0.5,<0.1"
|
python-multipart = ">=0.0.5,<0.1"
|
||||||
python-socketio = ">=5.7.0,<6.0"
|
python-socketio = ">=5.7.0,<6.0"
|
||||||
redis = ">=4.3.5,<6.0"
|
redis = ">=4.3.5,<6.0"
|
||||||
rich = ">=13.0.0,<14.0"
|
rich = ">=13.0.0,<14.0"
|
||||||
sqlmodel = ">=0.0.14,<0.1"
|
sqlmodel = ">=0.0.14,<0.1"
|
||||||
typer = ">=0.4.2,<1.0"
|
typer = ">=0.15.1,<1.0"
|
||||||
uvicorn = [
|
uvicorn = ">=0.20.0"
|
||||||
{version = "^0.24.0", python = ">=3.12"},
|
|
||||||
{version = "^0.20.0", python = "<3.12"},
|
|
||||||
]
|
|
||||||
watchdog = ">=2.3.1,<5.0"
|
|
||||||
watchfiles = ">=0.19.0,<1.0"
|
|
||||||
starlette-admin = ">=0.11.0,<1.0"
|
starlette-admin = ">=0.11.0,<1.0"
|
||||||
alembic = ">=1.11.1,<2.0"
|
alembic = ">=1.11.1,<2.0"
|
||||||
platformdirs = ">=3.10.0,<5.0"
|
platformdirs = ">=3.10.0,<5.0"
|
||||||
distro = {version = ">=1.8.0,<2.0", platform = "linux"}
|
distro = { version = ">=1.8.0,<2.0", platform = "linux" }
|
||||||
python-engineio = "!=4.6.0"
|
python-engineio = "!=4.6.0"
|
||||||
wrapt = [
|
wrapt = ">=1.17.0,<2.0"
|
||||||
{version = ">=1.14.0,<2.0", python = ">=3.11"},
|
|
||||||
{version = ">=1.11.0,<2.0", python = "<3.11"},
|
|
||||||
]
|
|
||||||
packaging = ">=23.1,<25.0"
|
packaging = ">=23.1,<25.0"
|
||||||
reflex-hosting-cli = ">=0.1.2,<2.0"
|
reflex-hosting-cli = ">=0.1.29"
|
||||||
charset-normalizer = ">=3.3.2,<4.0"
|
charset-normalizer = ">=3.3.2,<4.0"
|
||||||
wheel = ">=0.42.0,<1.0"
|
wheel = ">=0.42.0,<1.0"
|
||||||
build = ">=1.0.3,<2.0"
|
build = ">=1.0.3,<2.0"
|
||||||
setuptools = ">=69.1.1,<70.0"
|
setuptools = ">=75.0"
|
||||||
httpx = ">=0.25.1,<1.0"
|
httpx = ">=0.25.1,<1.0"
|
||||||
twine = ">=4.0.0,<6.0"
|
twine = ">=4.0.0,<7.0"
|
||||||
tomlkit = ">=0.12.4,<1.0"
|
tomlkit = ">=0.12.4,<1.0"
|
||||||
lazy_loader = ">=0.4"
|
lazy_loader = ">=0.4"
|
||||||
|
typing_extensions = ">=4.6.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = ">=7.1.2,<8.0"
|
pytest = ">=7.1.2,<9.0"
|
||||||
pytest-mock = ">=3.10.0,<4.0"
|
pytest-mock = ">=3.10.0,<4.0"
|
||||||
pyright = ">=1.1.229,<1.1.335"
|
pyright = ">=1.1.394, <1.2"
|
||||||
darglint = ">=1.8.1,<2.0"
|
darglint = ">=1.8.1,<2.0"
|
||||||
|
dill = ">=0.3.8"
|
||||||
toml = ">=0.10.2,<1.0"
|
toml = ">=0.10.2,<1.0"
|
||||||
pytest-asyncio = ">=0.20.1,<0.22.0" # https://github.com/pytest-dev/pytest-asyncio/issues/706
|
pytest-asyncio = ">=0.24.0"
|
||||||
pytest-cov = ">=4.0.0,<5.0"
|
pytest-cov = ">=4.0.0,<7.0"
|
||||||
black = "^22.10.0,<23.0"
|
ruff = "0.9.6"
|
||||||
ruff = "0.1.0"
|
pandas = ">=2.1.1,<3.0"
|
||||||
pandas = [
|
pillow = ">=10.0.0,<12.0"
|
||||||
{version = ">=2.1.1,<3.0", python = ">=3.9,<3.13"},
|
|
||||||
{version = ">=1.5.3,<2.0", python = ">=3.8,<3.9"},
|
|
||||||
]
|
|
||||||
pillow = [
|
|
||||||
{version = ">=10.0.0,<11.0", python = ">=3.8,<4.0"}
|
|
||||||
]
|
|
||||||
plotly = ">=5.13.0,<6.0"
|
plotly = ">=5.13.0,<6.0"
|
||||||
asynctest = ">=0.13.0,<1.0"
|
asynctest = ">=0.13.0,<1.0"
|
||||||
pre-commit = {version = ">=3.2.1", python = ">=3.8,<4.0"}
|
pre-commit = ">=3.2.1"
|
||||||
selenium = ">=4.11.0,<5.0"
|
selenium = ">=4.11.0,<5.0"
|
||||||
pytest-benchmark = ">=4.0.0,<5.0"
|
pytest-benchmark = ">=4.0.0,<6.0"
|
||||||
|
playwright = ">=1.46.0"
|
||||||
|
pytest-playwright = ">=0.5.1"
|
||||||
|
pytest-codspeed = "^3.1.2"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
reflex = "reflex.reflex:cli"
|
reflex = "reflex.reflex:cli"
|
||||||
@ -96,15 +78,60 @@ requires = ["poetry-core>=1.5.1"]
|
|||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
|
reportIncompatibleMethodOverride = false
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
exclude = ["*.pyi"]
|
target-version = "py310"
|
||||||
target-version = "py37"
|
output-format = "concise"
|
||||||
lint.select = ["B", "D", "E", "F", "I", "SIM", "W"]
|
lint.isort.split-on-trailing-comma = false
|
||||||
lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541"]
|
lint.select = [
|
||||||
|
"ANN001",
|
||||||
|
"B",
|
||||||
|
"C4",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"ERA",
|
||||||
|
"F",
|
||||||
|
"FURB",
|
||||||
|
"I",
|
||||||
|
"N",
|
||||||
|
"PERF",
|
||||||
|
"PGH",
|
||||||
|
"PTH",
|
||||||
|
"RUF",
|
||||||
|
"SIM",
|
||||||
|
"T",
|
||||||
|
"TRY",
|
||||||
|
"W",
|
||||||
|
]
|
||||||
|
lint.ignore = [
|
||||||
|
"B008",
|
||||||
|
"D205",
|
||||||
|
"E501",
|
||||||
|
"F403",
|
||||||
|
"SIM115",
|
||||||
|
"RUF006",
|
||||||
|
"RUF008",
|
||||||
|
"RUF012",
|
||||||
|
"TRY0",
|
||||||
|
]
|
||||||
|
lint.pydocstyle.convention = "google"
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["F401"]
|
"__init__.py" = ["F401"]
|
||||||
"tests/*.py" = ["D100", "D103", "D104", "B018"]
|
"tests/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
||||||
|
"benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
||||||
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
||||||
"*.pyi" = ["ALL"]
|
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"]
|
||||||
|
"pyi_generator.py" = ["N802"]
|
||||||
|
"reflex/constants/*.py" = ["N"]
|
||||||
"*/blank.py" = ["I001"]
|
"*/blank.py" = ["I001"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
filterwarnings = "ignore:fields may not start with an underscore:RuntimeWarning"
|
||||||
|
asyncio_default_fixture_loop_scope = "function"
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
|
||||||
|
[tool.codespell]
|
||||||
|
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
||||||
|
ignore-words-list = "te, TreeE"
|
||||||
|
4
reflex/.templates/apps/demo/.gitignore
vendored
4
reflex/.templates/apps/demo/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
*.db
|
|
||||||
*.py[cod]
|
|
||||||
.web
|
|
||||||
__pycache__/
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@ -1,10 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="Github" clip-path="url(#clip0_469_1929)">
|
|
||||||
<path id="Vector" d="M8.0004 0.587524C3.80139 0.587524 0.400391 3.98851 0.400391 8.1875C0.400391 11.5505 2.57589 14.391 5.59689 15.398C5.97689 15.4645 6.11939 15.2365 6.11939 15.037C6.11939 14.8565 6.10989 14.258 6.10989 13.6215C4.20039 13.973 3.70639 13.156 3.55439 12.7285C3.46889 12.51 3.09839 11.8355 2.77539 11.655C2.50939 11.5125 2.12939 11.161 2.76589 11.1515C3.36439 11.142 3.79189 11.7025 3.93439 11.9305C4.61839 13.08 5.71089 12.757 6.14789 12.5575C6.21439 12.0635 6.41388 11.731 6.6324 11.541C4.94139 11.351 3.17439 10.6955 3.17439 7.7885C3.17439 6.962 3.46889 6.27801 3.95339 5.74601C3.87739 5.55601 3.61139 4.77701 4.02939 3.73201C4.02939 3.73201 4.66589 3.53251 6.11939 4.51101C6.7274 4.34001 7.3734 4.25451 8.0194 4.25451C8.6654 4.25451 9.3114 4.34001 9.9194 4.51101C11.3729 3.52301 12.0094 3.73201 12.0094 3.73201C12.4274 4.77701 12.1614 5.55601 12.0854 5.74601C12.5699 6.27801 12.8644 6.9525 12.8644 7.7885C12.8644 10.705 11.0879 11.351 9.3969 11.541C9.6724 11.7785 9.9099 12.2345 9.9099 12.947C9.9099 13.9635 9.9004 14.7805 9.9004 15.037C9.9004 15.2365 10.0429 15.474 10.4229 15.398C13.5165 14.3536 15.5996 11.4527 15.6004 8.1875C15.6004 3.98851 12.1994 0.587524 8.0004 0.587524Z" fill="#494369"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_469_1929">
|
|
||||||
<rect width="16" height="16" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,37 +0,0 @@
|
|||||||
<svg width="67" height="14" viewBox="0 0 67 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="67" height="14" fill="#1E1E1E"/>
|
|
||||||
<g id="Nav Template > Initial" clip-path="url(#clip0_0_1)">
|
|
||||||
<rect width="1440" height="1024" transform="translate(-16 -17)" fill="white"/>
|
|
||||||
<g id="Sidebar">
|
|
||||||
<g clip-path="url(#clip1_0_1)">
|
|
||||||
<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
|
|
||||||
<g id="Header">
|
|
||||||
<path d="M-16 -17H264V31H-16V-17Z" fill="white"/>
|
|
||||||
<g id="Button">
|
|
||||||
<rect x="-4" y="-3" width="74.316" height="20" rx="6" fill="white"/>
|
|
||||||
<g id="Logo">
|
|
||||||
<g id="Reflex">
|
|
||||||
<path d="M0 13.6316V0.368408H10.6106V5.67369H7.95792V3.02105H2.65264V5.67369H7.95792V8.32633H2.65264V13.6316H0ZM7.95792 13.6316V8.32633H10.6106V13.6316H7.95792Z" fill="#110F1F"/>
|
|
||||||
<path d="M13.2632 13.6316V0.368408H21.2211V3.02105H15.9158V5.67369H21.2211V8.32633H15.9158V10.979H21.2211V13.6316H13.2632Z" fill="#110F1F"/>
|
|
||||||
<path d="M23.8738 13.6316V0.368408H31.8317V3.02105H26.5264V5.67369H31.8317V8.32633H26.5264V13.6316H23.8738Z" fill="#110F1F"/>
|
|
||||||
<path d="M34.4843 13.6316V0.368408H37.137V10.979H42.4422V13.6316H34.4843Z" fill="#110F1F"/>
|
|
||||||
<path d="M45.0949 13.6316V0.368408H53.0528V3.02105H47.7475V5.67369H53.0528V8.32633H47.7475V10.979H53.0528V13.6316H45.0949Z" fill="#110F1F"/>
|
|
||||||
<path d="M55.7054 5.67369V0.368408H58.3581V5.67369H55.7054ZM63.6634 5.67369V0.368408H66.316V5.67369H63.6634ZM58.3581 8.32633V5.67369H63.6634V8.32633H58.3581ZM55.7054 13.6316V8.32633H58.3581V13.6316H55.7054ZM63.6634 13.6316V8.32633H66.316V13.6316H63.6634Z" fill="#110F1F"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<path d="M264 30.5H-16V31.5H264V30.5Z" fill="#F4F3F6"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<path d="M263.5 -17V1007H264.5V-17H263.5Z" fill="#F4F3F6"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_0_1">
|
|
||||||
<rect width="1440" height="1024" fill="white" transform="translate(-16 -17)"/>
|
|
||||||
</clipPath>
|
|
||||||
<clipPath id="clip1_0_1">
|
|
||||||
<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.9 KiB |
@ -1,68 +0,0 @@
|
|||||||
<svg width="80" height="78" viewBox="0 0 80 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g filter="url(#filter0_ddddi_449_2821)">
|
|
||||||
<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint0_radial_449_2821)"/>
|
|
||||||
<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint1_radial_449_2821)"/>
|
|
||||||
<g filter="url(#filter1_i_449_2821)">
|
|
||||||
<path d="M31 37.5C30.4477 37.5 30 37.0523 30 36.5V13.5001C30 12.9478 30.4477 12.5001 31 12.5001H49C49.5523 12.5001 50 12.9478 50 13.5001V21.5001C50 22.0524 49.5523 22.5001 49 22.5001H45V18.5001C45 17.9478 44.5523 17.5001 44 17.5001H36C35.4477 17.5001 35 17.9478 35 18.5001V21.5001C35 22.0524 35.4477 22.5001 36 22.5001H45V27.5001H36C35.4477 27.5001 35 27.9478 35 28.5001V36.5C35 37.0523 34.5523 37.5 34 37.5H31ZM46 37.5C45.4477 37.5 45 37.0523 45 36.5V27.5001H49C49.5523 27.5001 50 27.9478 50 28.5001V36.5C50 37.0523 49.5523 37.5 49 37.5H46Z" fill="url(#paint2_radial_449_2821)"/>
|
|
||||||
</g>
|
|
||||||
<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" stroke="#20117E" stroke-opacity="0.04"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_ddddi_449_2821" x="0.5" y="0.5" width="79" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect1_dropShadow_449_2821"/>
|
|
||||||
<feOffset dy="10"/>
|
|
||||||
<feGaussianBlur stdDeviation="8"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.06 0"/>
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_449_2821"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feMorphology radius="6" operator="erode" in="SourceAlpha" result="effect2_dropShadow_449_2821"/>
|
|
||||||
<feOffset dy="12"/>
|
|
||||||
<feGaussianBlur stdDeviation="3"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.1 0"/>
|
|
||||||
<feBlend mode="normal" in2="effect1_dropShadow_449_2821" result="effect2_dropShadow_449_2821"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect3_dropShadow_449_2821"/>
|
|
||||||
<feOffset dy="10"/>
|
|
||||||
<feGaussianBlur stdDeviation="3"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.16 0"/>
|
|
||||||
<feBlend mode="normal" in2="effect2_dropShadow_449_2821" result="effect3_dropShadow_449_2821"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect4_dropShadow_449_2821"/>
|
|
||||||
<feOffset dy="2"/>
|
|
||||||
<feGaussianBlur stdDeviation="1"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.05 0"/>
|
|
||||||
<feBlend mode="normal" in2="effect3_dropShadow_449_2821" result="effect4_dropShadow_449_2821"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_449_2821" result="shape"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset dy="-8"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.678431 0 0 0 0 0.607843 0 0 0 0 0.972549 0 0 0 0.2 0"/>
|
|
||||||
<feBlend mode="normal" in2="shape" result="effect5_innerShadow_449_2821"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter1_i_449_2821" x="30" y="12.5001" width="20" height="26.9999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset dy="2"/>
|
|
||||||
<feGaussianBlur stdDeviation="1.5"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.32 0"/>
|
|
||||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_449_2821"/>
|
|
||||||
</filter>
|
|
||||||
<radialGradient id="paint0_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
|
|
||||||
<stop stop-color="white" stop-opacity="0.9"/>
|
|
||||||
<stop offset="1" stop-color="#4E3DB9" stop-opacity="0.24"/>
|
|
||||||
</radialGradient>
|
|
||||||
<radialGradient id="paint1_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
|
|
||||||
<stop stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="#F7F7F7"/>
|
|
||||||
</radialGradient>
|
|
||||||
<radialGradient id="paint2_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 12.5001) rotate(90) scale(24.9999 20)">
|
|
||||||
<stop stop-color="#F5F3FF"/>
|
|
||||||
<stop stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="#E1DDF4"/>
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 5.3 KiB |
@ -1,13 +0,0 @@
|
|||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="PaneLeft" clip-path="url(#clip0_469_1942)">
|
|
||||||
<g id="Vector">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.80217 0.525009C7.34654 0.525009 6.97717 0.894373 6.97717 1.35001V10.65C6.97717 11.1056 7.34654 11.475 7.80217 11.475H10.6522C11.1078 11.475 11.4772 11.1056 11.4772 10.65V1.35001C11.4772 0.894373 11.1078 0.525009 10.6522 0.525009H7.80217ZM8.02717 10.425V1.57501H10.4272V10.425H8.02717Z" fill="#494369"/>
|
|
||||||
<path d="M3.78215 8.14502L2.16213 6.525H5.92717V5.475H2.16213L3.78215 3.85498L3.03969 3.11252L0.523438 5.62877V6.37123L3.03969 8.88748L3.78215 8.14502Z" fill="#494369"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_469_1942">
|
|
||||||
<rect width="12" height="12" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 807 B |
@ -1 +0,0 @@
|
|||||||
"""Base template for Reflex."""
|
|
@ -1,126 +0,0 @@
|
|||||||
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from .pages import chatapp_page, datatable_page, forms_page, graphing_page, home_page
|
|
||||||
from .sidebar import sidebar
|
|
||||||
from .state import State
|
|
||||||
from .styles import *
|
|
||||||
|
|
||||||
meta = [
|
|
||||||
{
|
|
||||||
"name": "viewport",
|
|
||||||
"content": "width=device-width, shrink-to-fit=no, initial-scale=1",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def template(main_content: Callable[[], rx.Component]) -> rx.Component:
|
|
||||||
"""The template for each page of the app.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
main_content (Callable[[], rx.Component]): The main content of the page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The template for each page of the app.
|
|
||||||
"""
|
|
||||||
menu_button = rx.chakra.box(
|
|
||||||
rx.chakra.menu(
|
|
||||||
rx.chakra.menu_button(
|
|
||||||
rx.chakra.icon(
|
|
||||||
tag="hamburger",
|
|
||||||
size="4em",
|
|
||||||
color=text_color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.menu_list(
|
|
||||||
rx.chakra.menu_item(rx.chakra.link("Home", href="/", width="100%")),
|
|
||||||
rx.chakra.menu_divider(),
|
|
||||||
rx.chakra.menu_item(
|
|
||||||
rx.chakra.link(
|
|
||||||
"About", href="https://github.com/reflex-dev", width="100%"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
rx.chakra.menu_item(
|
|
||||||
rx.chakra.link(
|
|
||||||
"Contact", href="mailto:founders@reflex.dev", width="100%"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
position="fixed",
|
|
||||||
right="1.5em",
|
|
||||||
top="1.5em",
|
|
||||||
z_index="500",
|
|
||||||
)
|
|
||||||
|
|
||||||
return rx.chakra.hstack(
|
|
||||||
sidebar(),
|
|
||||||
main_content(),
|
|
||||||
rx.chakra.spacer(),
|
|
||||||
menu_button,
|
|
||||||
align_items="flex-start",
|
|
||||||
transition="left 0.5s, width 0.5s",
|
|
||||||
position="relative",
|
|
||||||
left=rx.cond(State.sidebar_displayed, "0px", f"-{sidebar_width}"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@rx.page("/", meta=meta)
|
|
||||||
@template
|
|
||||||
def home() -> rx.Component:
|
|
||||||
"""Home page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The home page.
|
|
||||||
"""
|
|
||||||
return home_page()
|
|
||||||
|
|
||||||
|
|
||||||
@rx.page("/forms", meta=meta)
|
|
||||||
@template
|
|
||||||
def forms() -> rx.Component:
|
|
||||||
"""Forms page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The settings page.
|
|
||||||
"""
|
|
||||||
return forms_page()
|
|
||||||
|
|
||||||
|
|
||||||
@rx.page("/graphing", meta=meta)
|
|
||||||
@template
|
|
||||||
def graphing() -> rx.Component:
|
|
||||||
"""Graphing page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The graphing page.
|
|
||||||
"""
|
|
||||||
return graphing_page()
|
|
||||||
|
|
||||||
|
|
||||||
@rx.page("/datatable", meta=meta)
|
|
||||||
@template
|
|
||||||
def datatable() -> rx.Component:
|
|
||||||
"""Data Table page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The chatapp page.
|
|
||||||
"""
|
|
||||||
return datatable_page()
|
|
||||||
|
|
||||||
|
|
||||||
@rx.page("/chatapp", meta=meta)
|
|
||||||
@template
|
|
||||||
def chatapp() -> rx.Component:
|
|
||||||
"""Chatapp page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The chatapp page.
|
|
||||||
"""
|
|
||||||
return chatapp_page()
|
|
||||||
|
|
||||||
|
|
||||||
# Create the app.
|
|
||||||
app = rx.App(style=base_style)
|
|
@ -1,6 +0,0 @@
|
|||||||
"""The pages of the app."""
|
|
||||||
from .chatapp import chatapp_page
|
|
||||||
from .datatable import datatable_page
|
|
||||||
from .forms import forms_page
|
|
||||||
from .graphing import graphing_page
|
|
||||||
from .home import home_page
|
|
@ -1,31 +0,0 @@
|
|||||||
"""The main Chat app."""
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ..styles import *
|
|
||||||
from ..webui import styles
|
|
||||||
from ..webui.components import chat, modal, navbar, sidebar
|
|
||||||
|
|
||||||
|
|
||||||
def chatapp_page() -> rx.Component:
|
|
||||||
"""The main app.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The UI for the main app.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
navbar(),
|
|
||||||
chat.chat(),
|
|
||||||
chat.action_bar(),
|
|
||||||
sidebar(),
|
|
||||||
modal(),
|
|
||||||
bg=styles.bg_dark_color,
|
|
||||||
color=styles.text_light_color,
|
|
||||||
min_h="100vh",
|
|
||||||
align_items="stretch",
|
|
||||||
spacing="0",
|
|
||||||
style=template_content_style,
|
|
||||||
),
|
|
||||||
style=template_page_style,
|
|
||||||
)
|
|
@ -1,359 +0,0 @@
|
|||||||
"""The settings page for the template."""
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
from reflex.components.datadisplay.dataeditor import DataEditorTheme
|
|
||||||
|
|
||||||
from ..styles import *
|
|
||||||
from ..webui.state import State
|
|
||||||
|
|
||||||
|
|
||||||
class DataTableState(State):
|
|
||||||
"""Datatable state."""
|
|
||||||
|
|
||||||
cols: list[Any] = [
|
|
||||||
{"title": "Title", "type": "str"},
|
|
||||||
{
|
|
||||||
"title": "Name",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Birth",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Human",
|
|
||||||
"type": "bool",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "House",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Wand",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 250,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Patronus",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Blood status",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 200,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
data = [
|
|
||||||
[
|
|
||||||
"1",
|
|
||||||
"Harry James Potter",
|
|
||||||
"31 July 1980",
|
|
||||||
True,
|
|
||||||
"Gryffindor",
|
|
||||||
"11' Holly phoenix feather",
|
|
||||||
"Stag",
|
|
||||||
"Half-blood",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"2",
|
|
||||||
"Ronald Bilius Weasley",
|
|
||||||
"1 March 1980",
|
|
||||||
True,
|
|
||||||
"Gryffindor",
|
|
||||||
"12' Ash unicorn tail hair",
|
|
||||||
"Jack Russell terrier",
|
|
||||||
"Pure-blood",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"3",
|
|
||||||
"Hermione Jean Granger",
|
|
||||||
"19 September, 1979",
|
|
||||||
True,
|
|
||||||
"Gryffindor",
|
|
||||||
"10¾' vine wood dragon heartstring",
|
|
||||||
"Otter",
|
|
||||||
"Muggle-born",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"4",
|
|
||||||
"Albus Percival Wulfric Brian Dumbledore",
|
|
||||||
"Late August 1881",
|
|
||||||
True,
|
|
||||||
"Gryffindor",
|
|
||||||
"15' Elder Thestral tail hair core",
|
|
||||||
"Phoenix",
|
|
||||||
"Half-blood",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"5",
|
|
||||||
"Rubeus Hagrid",
|
|
||||||
"6 December 1928",
|
|
||||||
False,
|
|
||||||
"Gryffindor",
|
|
||||||
"16' Oak unknown core",
|
|
||||||
"None",
|
|
||||||
"Part-Human (Half-giant)",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"6",
|
|
||||||
"Fred Weasley",
|
|
||||||
"1 April, 1978",
|
|
||||||
True,
|
|
||||||
"Gryffindor",
|
|
||||||
"Unknown",
|
|
||||||
"Unknown",
|
|
||||||
"Pure-blood",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"7",
|
|
||||||
"George Weasley",
|
|
||||||
"1 April, 1978",
|
|
||||||
True,
|
|
||||||
"Gryffindor",
|
|
||||||
"Unknown",
|
|
||||||
"Unknown",
|
|
||||||
"Pure-blood",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
code_show = """rx.chakra.hstack(
|
|
||||||
rx.chakra.divider(orientation="vertical", height="100vh", border="solid black 1px"),
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.box(
|
|
||||||
rx.data_editor(
|
|
||||||
columns=DataTableState.cols,
|
|
||||||
data=DataTableState.data,
|
|
||||||
draw_focus_ring=True,
|
|
||||||
row_height=50,
|
|
||||||
smooth_scroll_x=True,
|
|
||||||
smooth_scroll_y=True,
|
|
||||||
column_select="single",
|
|
||||||
# style
|
|
||||||
theme=DataEditorTheme(**darkTheme),
|
|
||||||
width="80vw",
|
|
||||||
height="80vh",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.spacer(),
|
|
||||||
height="100vh",
|
|
||||||
spacing="25",
|
|
||||||
),
|
|
||||||
)"""
|
|
||||||
|
|
||||||
state_show = """class DataTableState(State):
|
|
||||||
cols: list[Any] = [
|
|
||||||
{"title": "Title", "type": "str"},
|
|
||||||
{
|
|
||||||
"title": "Name",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Birth",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Human",
|
|
||||||
"type": "bool",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "House",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Wand",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 250,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Patronus",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Blood status",
|
|
||||||
"type": "str",
|
|
||||||
"group": "Data",
|
|
||||||
"width": 200,
|
|
||||||
},
|
|
||||||
]"""
|
|
||||||
|
|
||||||
data_show = """[
|
|
||||||
["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"],
|
|
||||||
["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"],
|
|
||||||
["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"],
|
|
||||||
["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"],
|
|
||||||
["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"],
|
|
||||||
["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
|
|
||||||
["7", "George Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
|
|
||||||
]"""
|
|
||||||
|
|
||||||
|
|
||||||
darkTheme = {
|
|
||||||
"accent_color": "#8c96ff",
|
|
||||||
"accent_light": "rgba(202, 206, 255, 0.253)",
|
|
||||||
"text_dark": "#ffffff",
|
|
||||||
"text_medium": "#b8b8b8",
|
|
||||||
"text_light": "#a0a0a0",
|
|
||||||
"text_bubble": "#ffffff",
|
|
||||||
"bg_icon_header": "#b8b8b8",
|
|
||||||
"fg_icon_header": "#000000",
|
|
||||||
"text_header": "#a1a1a1",
|
|
||||||
"text_header_selected": "#000000",
|
|
||||||
"bg_cell": "#16161b",
|
|
||||||
"bg_cell_medium": "#202027",
|
|
||||||
"bg_header": "#212121",
|
|
||||||
"bg_header_has_focus": "#474747",
|
|
||||||
"bg_header_hovered": "#404040",
|
|
||||||
"bg_bubble": "#212121",
|
|
||||||
"bg_bubble_selected": "#000000",
|
|
||||||
"bg_search_result": "#423c24",
|
|
||||||
"border_color": "rgba(225,225,225,0.2)",
|
|
||||||
"drilldown_border": "rgba(225,225,225,0.4)",
|
|
||||||
"link_color": "#4F5DFF",
|
|
||||||
"header_font_style": "bold 14px",
|
|
||||||
"base_font_style": "13px",
|
|
||||||
"font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
|
|
||||||
}
|
|
||||||
|
|
||||||
darkTheme_show = """darkTheme={
|
|
||||||
"accent_color": "#8c96ff",
|
|
||||||
"accent_light": "rgba(202, 206, 255, 0.253)",
|
|
||||||
"text_dark": "#ffffff",
|
|
||||||
"text_medium": "#b8b8b8",
|
|
||||||
"text_light": "#a0a0a0",
|
|
||||||
"text_bubble": "#ffffff",
|
|
||||||
"bg_icon_header": "#b8b8b8",
|
|
||||||
"fg_icon_header": "#000000",
|
|
||||||
"text_header": "#a1a1a1",
|
|
||||||
"text_header_selected": "#000000",
|
|
||||||
"bg_cell": "#16161b",
|
|
||||||
"bg_cell_medium": "#202027",
|
|
||||||
"bg_header": "#212121",
|
|
||||||
"bg_header_has_focus": "#474747",
|
|
||||||
"bg_header_hovered": "#404040",
|
|
||||||
"bg_bubble": "#212121",
|
|
||||||
"bg_bubble_selected": "#000000",
|
|
||||||
"bg_search_result": "#423c24",
|
|
||||||
"border_color": "rgba(225,225,225,0.2)",
|
|
||||||
"drilldown_border": "rgba(225,225,225,0.4)",
|
|
||||||
"link_color": "#4F5DFF",
|
|
||||||
"header_font_style": "bold 14px",
|
|
||||||
"base_font_style": "13px",
|
|
||||||
"font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
|
|
||||||
}"""
|
|
||||||
|
|
||||||
|
|
||||||
def datatable_page() -> rx.Component:
|
|
||||||
"""The UI for the settings page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The UI for the settings page.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.heading(
|
|
||||||
"Data Table Demo",
|
|
||||||
font_size="3em",
|
|
||||||
),
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.box(
|
|
||||||
rx.data_editor(
|
|
||||||
columns=DataTableState.cols,
|
|
||||||
data=DataTableState.data,
|
|
||||||
draw_focus_ring=True,
|
|
||||||
row_height=50,
|
|
||||||
smooth_scroll_x=True,
|
|
||||||
smooth_scroll_y=True,
|
|
||||||
column_select="single",
|
|
||||||
# style
|
|
||||||
theme=DataEditorTheme(**darkTheme),
|
|
||||||
width="80vw",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.spacer(),
|
|
||||||
spacing="25",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.tabs(
|
|
||||||
rx.chakra.tab_list(
|
|
||||||
rx.chakra.tab("Code", style=tab_style),
|
|
||||||
rx.chakra.tab("Data", style=tab_style),
|
|
||||||
rx.chakra.tab("State", style=tab_style),
|
|
||||||
rx.chakra.tab("Styling", style=tab_style),
|
|
||||||
padding_x=0,
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panels(
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
code_show,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
data_show,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
state_show,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
darkTheme_show,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
variant="unstyled",
|
|
||||||
color_scheme="purple",
|
|
||||||
align="end",
|
|
||||||
width="100%",
|
|
||||||
padding_top=".5em",
|
|
||||||
),
|
|
||||||
style=template_content_style,
|
|
||||||
),
|
|
||||||
style=template_page_style,
|
|
||||||
)
|
|
@ -1,256 +0,0 @@
|
|||||||
"""The settings page for the template."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ..states.form_state import FormState, UploadState
|
|
||||||
from ..styles import *
|
|
||||||
|
|
||||||
forms_1_code = """rx.chakra.vstack(
|
|
||||||
rx.chakra.form(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.input(
|
|
||||||
placeholder="First Name",
|
|
||||||
id="first_name",
|
|
||||||
),
|
|
||||||
rx.chakra.input(
|
|
||||||
placeholder="Last Name", id="last_name"
|
|
||||||
),
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.checkbox("Checked", id="check"),
|
|
||||||
rx.chakra.switch("Switched", id="switch"),
|
|
||||||
),
|
|
||||||
rx.chakra.button("Submit",
|
|
||||||
type_="submit",
|
|
||||||
bg="#ecfdf5",
|
|
||||||
color="#047857",
|
|
||||||
border_radius="lg",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
on_submit=FormState.handle_submit,
|
|
||||||
),
|
|
||||||
rx.chakra.divider(),
|
|
||||||
rx.chakra.heading("Results"),
|
|
||||||
rx.chakra.text(FormState.form_data.to_string()),
|
|
||||||
width="100%",
|
|
||||||
)"""
|
|
||||||
|
|
||||||
color = "rgb(107,99,246)"
|
|
||||||
|
|
||||||
forms_1_state = """class FormState(rx.State):
|
|
||||||
|
|
||||||
form_data: dict = {}
|
|
||||||
|
|
||||||
def handle_submit(self, form_data: dict):
|
|
||||||
"Handle the form submit."
|
|
||||||
self.form_data = form_data"""
|
|
||||||
|
|
||||||
|
|
||||||
forms_2_code = """rx.chakra.vstack(
|
|
||||||
rx.upload(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.button(
|
|
||||||
"Select File",
|
|
||||||
color=color,
|
|
||||||
bg="white",
|
|
||||||
border=f"1px solid {color}",
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
"Drag and drop files here or click to select files"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
border=f"1px dotted {color}",
|
|
||||||
padding="5em",
|
|
||||||
),
|
|
||||||
rx.chakra.hstack(rx.foreach(rx.selected_files, rx.chakra.text)),
|
|
||||||
rx.chakra.button(
|
|
||||||
"Upload",
|
|
||||||
on_click=lambda: UploadState.handle_upload(
|
|
||||||
rx.upload_files()
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.button(
|
|
||||||
"Clear",
|
|
||||||
on_click=rx.clear_selected_files,
|
|
||||||
),
|
|
||||||
rx.foreach(
|
|
||||||
UploadState.img, lambda img: rx.chakra.image(src=img, width="20%", height="auto",)
|
|
||||||
),
|
|
||||||
padding="5em",
|
|
||||||
width="100%",
|
|
||||||
)"""
|
|
||||||
|
|
||||||
forms_2_state = """class UploadState(State):
|
|
||||||
"The app state."
|
|
||||||
|
|
||||||
# The images to show.
|
|
||||||
img: list[str]
|
|
||||||
|
|
||||||
async def handle_upload(
|
|
||||||
self, files: list[rx.UploadFile]
|
|
||||||
):
|
|
||||||
"Handle the upload of file(s).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
files: The uploaded files.
|
|
||||||
"
|
|
||||||
for file in files:
|
|
||||||
upload_data = await file.read()
|
|
||||||
outfile = rx.get_asset_path(file.filename)
|
|
||||||
# Save the file.
|
|
||||||
with open(outfile, "wb") as file_object:
|
|
||||||
file_object.write(upload_data)
|
|
||||||
|
|
||||||
# Update the img var.
|
|
||||||
self.img.append(f"/{file.filename}")"""
|
|
||||||
|
|
||||||
|
|
||||||
def forms_page() -> rx.Component:
|
|
||||||
"""The UI for the settings page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The UI for the settings page.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.heading(
|
|
||||||
"Forms Demo",
|
|
||||||
font_size="3em",
|
|
||||||
),
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.form(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.input(
|
|
||||||
placeholder="First Name",
|
|
||||||
id="first_name",
|
|
||||||
),
|
|
||||||
rx.chakra.input(placeholder="Last Name", id="last_name"),
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.checkbox("Checked", id="check"),
|
|
||||||
rx.chakra.switch("Switched", id="switch"),
|
|
||||||
),
|
|
||||||
rx.chakra.button(
|
|
||||||
"Submit",
|
|
||||||
type_="submit",
|
|
||||||
bg="#ecfdf5",
|
|
||||||
color="#047857",
|
|
||||||
border_radius="lg",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
on_submit=FormState.handle_submit,
|
|
||||||
),
|
|
||||||
rx.chakra.divider(),
|
|
||||||
rx.chakra.heading("Results"),
|
|
||||||
rx.chakra.text(FormState.form_data.to_string()),
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.chakra.tabs(
|
|
||||||
rx.chakra.tab_list(
|
|
||||||
rx.chakra.tab("Code", style=tab_style),
|
|
||||||
rx.chakra.tab("State", style=tab_style),
|
|
||||||
padding_x=0,
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panels(
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
forms_1_code,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
forms_1_state,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
variant="unstyled",
|
|
||||||
color_scheme="purple",
|
|
||||||
align="end",
|
|
||||||
width="100%",
|
|
||||||
padding_top=".5em",
|
|
||||||
),
|
|
||||||
rx.chakra.heading("Upload Example", font_size="3em"),
|
|
||||||
rx.chakra.text("Try uploading some images and see how they look."),
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.upload(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.button(
|
|
||||||
"Select File",
|
|
||||||
color=color,
|
|
||||||
bg="white",
|
|
||||||
border=f"1px solid {color}",
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
"Drag and drop files here or click to select files"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
border=f"1px dotted {color}",
|
|
||||||
padding="5em",
|
|
||||||
),
|
|
||||||
rx.chakra.hstack(rx.foreach(rx.selected_files, rx.chakra.text)),
|
|
||||||
rx.chakra.button(
|
|
||||||
"Upload",
|
|
||||||
on_click=lambda: UploadState.handle_upload(rx.upload_files()),
|
|
||||||
),
|
|
||||||
rx.chakra.button(
|
|
||||||
"Clear",
|
|
||||||
on_click=rx.clear_selected_files,
|
|
||||||
),
|
|
||||||
rx.foreach(
|
|
||||||
UploadState.img,
|
|
||||||
lambda img: rx.chakra.image(
|
|
||||||
src=img,
|
|
||||||
width="20%",
|
|
||||||
height="auto",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding="5em",
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.chakra.tabs(
|
|
||||||
rx.chakra.tab_list(
|
|
||||||
rx.chakra.tab("Code", style=tab_style),
|
|
||||||
rx.chakra.tab("State", style=tab_style),
|
|
||||||
padding_x=0,
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panels(
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
forms_2_code,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
forms_2_state,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
variant="unstyled",
|
|
||||||
color_scheme="purple",
|
|
||||||
align="end",
|
|
||||||
width="100%",
|
|
||||||
padding_top=".5em",
|
|
||||||
),
|
|
||||||
style=template_content_style,
|
|
||||||
),
|
|
||||||
style=template_page_style,
|
|
||||||
)
|
|
@ -1,252 +0,0 @@
|
|||||||
"""The dashboard page for the template."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ..states.pie_state import PieChartState
|
|
||||||
from ..styles import *
|
|
||||||
|
|
||||||
data_1 = [
|
|
||||||
{"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
|
|
||||||
{"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
|
|
||||||
{"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
|
|
||||||
{"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
|
|
||||||
{"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
|
|
||||||
{"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
|
|
||||||
{"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
|
|
||||||
]
|
|
||||||
data_1_show = """[
|
|
||||||
{"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
|
|
||||||
{"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
|
|
||||||
{"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
|
|
||||||
{"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
|
|
||||||
{"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
|
|
||||||
{"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
|
|
||||||
{"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
|
|
||||||
]"""
|
|
||||||
|
|
||||||
|
|
||||||
graph_1_code = """rx.recharts.composed_chart(
|
|
||||||
rx.recharts.area(
|
|
||||||
data_key="uv", stroke="#8884d8", fill="#8884d8"
|
|
||||||
),
|
|
||||||
rx.recharts.bar(
|
|
||||||
data_key="amt", bar_size=20, fill="#413ea0"
|
|
||||||
),
|
|
||||||
rx.recharts.line(
|
|
||||||
data_key="pv", type_="monotone", stroke="#ff7300"
|
|
||||||
),
|
|
||||||
rx.recharts.x_axis(data_key="name"),
|
|
||||||
rx.recharts.y_axis(),
|
|
||||||
rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
|
|
||||||
rx.recharts.graphing_tooltip(),
|
|
||||||
data=data,
|
|
||||||
)"""
|
|
||||||
|
|
||||||
|
|
||||||
graph_2_code = """rx.recharts.pie_chart(
|
|
||||||
rx.recharts.pie(
|
|
||||||
data=PieChartState.resources,
|
|
||||||
data_key="count",
|
|
||||||
name_key="type_",
|
|
||||||
cx="50%",
|
|
||||||
cy="50%",
|
|
||||||
start_angle=180,
|
|
||||||
end_angle=0,
|
|
||||||
fill="#8884d8",
|
|
||||||
label=True,
|
|
||||||
),
|
|
||||||
rx.recharts.graphing_tooltip(),
|
|
||||||
),
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.foreach(
|
|
||||||
PieChartState.resource_types,
|
|
||||||
lambda type_, i: rx.chakra.hstack(
|
|
||||||
rx.chakra.button(
|
|
||||||
"-",
|
|
||||||
on_click=PieChartState.decrement(type_),
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
type_,
|
|
||||||
PieChartState.resources[i]["count"],
|
|
||||||
),
|
|
||||||
rx.chakra.button(
|
|
||||||
"+",
|
|
||||||
on_click=PieChartState.increment(type_),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)"""
|
|
||||||
|
|
||||||
graph_2_state = """class PieChartState(rx.State):
|
|
||||||
resources: list[dict[str, Any]] = [
|
|
||||||
dict(type_="🏆", count=1),
|
|
||||||
dict(type_="🪵", count=1),
|
|
||||||
dict(type_="🥑", count=1),
|
|
||||||
dict(type_="🧱", count=1),
|
|
||||||
]
|
|
||||||
|
|
||||||
@rx.cached_var
|
|
||||||
def resource_types(self) -> list[str]:
|
|
||||||
return [r["type_"] for r in self.resources]
|
|
||||||
|
|
||||||
def increment(self, type_: str):
|
|
||||||
for resource in self.resources:
|
|
||||||
if resource["type_"] == type_:
|
|
||||||
resource["count"] += 1
|
|
||||||
break
|
|
||||||
|
|
||||||
def decrement(self, type_: str):
|
|
||||||
for resource in self.resources:
|
|
||||||
if (
|
|
||||||
resource["type_"] == type_
|
|
||||||
and resource["count"] > 0
|
|
||||||
):
|
|
||||||
resource["count"] -= 1
|
|
||||||
break
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def graphing_page() -> rx.Component:
|
|
||||||
"""The UI for the dashboard page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The UI for the dashboard page.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.heading(
|
|
||||||
"Graphing Demo",
|
|
||||||
font_size="3em",
|
|
||||||
),
|
|
||||||
rx.chakra.heading(
|
|
||||||
"Composed Chart",
|
|
||||||
font_size="2em",
|
|
||||||
),
|
|
||||||
rx.chakra.stack(
|
|
||||||
rx.recharts.composed_chart(
|
|
||||||
rx.recharts.area(data_key="uv", stroke="#8884d8", fill="#8884d8"),
|
|
||||||
rx.recharts.bar(data_key="amt", bar_size=20, fill="#413ea0"),
|
|
||||||
rx.recharts.line(data_key="pv", type_="monotone", stroke="#ff7300"),
|
|
||||||
rx.recharts.x_axis(data_key="name"),
|
|
||||||
rx.recharts.y_axis(),
|
|
||||||
rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
|
|
||||||
rx.recharts.graphing_tooltip(),
|
|
||||||
data=data_1,
|
|
||||||
# height="15em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="20em",
|
|
||||||
),
|
|
||||||
rx.chakra.tabs(
|
|
||||||
rx.chakra.tab_list(
|
|
||||||
rx.chakra.tab("Code", style=tab_style),
|
|
||||||
rx.chakra.tab("Data", style=tab_style),
|
|
||||||
padding_x=0,
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panels(
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
graph_1_code,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
data_1_show,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
variant="unstyled",
|
|
||||||
color_scheme="purple",
|
|
||||||
align="end",
|
|
||||||
width="100%",
|
|
||||||
padding_top=".5em",
|
|
||||||
),
|
|
||||||
rx.chakra.heading("Interactive Example", font_size="2em"),
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.recharts.pie_chart(
|
|
||||||
rx.recharts.pie(
|
|
||||||
data=PieChartState.resources,
|
|
||||||
data_key="count",
|
|
||||||
name_key="type_",
|
|
||||||
cx="50%",
|
|
||||||
cy="50%",
|
|
||||||
start_angle=180,
|
|
||||||
end_angle=0,
|
|
||||||
fill="#8884d8",
|
|
||||||
label=True,
|
|
||||||
),
|
|
||||||
rx.recharts.graphing_tooltip(),
|
|
||||||
),
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.foreach(
|
|
||||||
PieChartState.resource_types,
|
|
||||||
lambda type_, i: rx.chakra.hstack(
|
|
||||||
rx.chakra.button(
|
|
||||||
"-",
|
|
||||||
on_click=PieChartState.decrement(type_),
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
type_,
|
|
||||||
PieChartState.resources[i]["count"],
|
|
||||||
),
|
|
||||||
rx.chakra.button(
|
|
||||||
"+",
|
|
||||||
on_click=PieChartState.increment(type_),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
height="15em",
|
|
||||||
),
|
|
||||||
rx.chakra.tabs(
|
|
||||||
rx.chakra.tab_list(
|
|
||||||
rx.chakra.tab("Code", style=tab_style),
|
|
||||||
rx.chakra.tab("State", style=tab_style),
|
|
||||||
padding_x=0,
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panels(
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
graph_2_code,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
rx.chakra.tab_panel(
|
|
||||||
rx.code_block(
|
|
||||||
graph_2_state,
|
|
||||||
language="python",
|
|
||||||
show_line_numbers=True,
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
padding_x=0,
|
|
||||||
padding_y=".25em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
variant="unstyled",
|
|
||||||
color_scheme="purple",
|
|
||||||
align="end",
|
|
||||||
width="100%",
|
|
||||||
padding_top=".5em",
|
|
||||||
),
|
|
||||||
style=template_content_style,
|
|
||||||
min_h="100vh",
|
|
||||||
),
|
|
||||||
style=template_page_style,
|
|
||||||
min_h="100vh",
|
|
||||||
)
|
|
@ -1,55 +0,0 @@
|
|||||||
"""The home page of the app."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ..styles import *
|
|
||||||
|
|
||||||
|
|
||||||
def home_page() -> rx.Component:
|
|
||||||
"""The UI for the home page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The UI for the home page.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.heading(
|
|
||||||
"Welcome to Reflex! 👋",
|
|
||||||
font_size="3em",
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
"Reflex is an open-source app framework built specifically to allow you to build web apps in pure python. 👈 Select a demo from the sidebar to see some examples of what Reflex can do!",
|
|
||||||
),
|
|
||||||
rx.chakra.heading(
|
|
||||||
"Things to check out:",
|
|
||||||
font_size="2em",
|
|
||||||
),
|
|
||||||
rx.chakra.unordered_list(
|
|
||||||
rx.chakra.list_item(
|
|
||||||
"Take a look at ",
|
|
||||||
rx.chakra.link(
|
|
||||||
"reflex.dev",
|
|
||||||
href="https://reflex.dev",
|
|
||||||
color="rgb(107,99,246)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.list_item(
|
|
||||||
"Check out our ",
|
|
||||||
rx.chakra.link(
|
|
||||||
"docs",
|
|
||||||
href="https://reflex.dev/docs/getting-started/introduction/",
|
|
||||||
color="rgb(107,99,246)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.list_item(
|
|
||||||
"Ask a question in our ",
|
|
||||||
rx.chakra.link(
|
|
||||||
"community",
|
|
||||||
href="https://discord.gg/T5WSbC2YtQ",
|
|
||||||
color="rgb(107,99,246)",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style=template_content_style,
|
|
||||||
),
|
|
||||||
style=template_page_style,
|
|
||||||
)
|
|
@ -1,178 +0,0 @@
|
|||||||
"""Sidebar component for the app."""
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from .state import State
|
|
||||||
from .styles import *
|
|
||||||
|
|
||||||
|
|
||||||
def sidebar_header() -> rx.Component:
|
|
||||||
"""Sidebar header.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The sidebar header component.
|
|
||||||
"""
|
|
||||||
return rx.chakra.hstack(
|
|
||||||
rx.chakra.image(
|
|
||||||
src="/icon.svg",
|
|
||||||
height="2em",
|
|
||||||
),
|
|
||||||
rx.chakra.spacer(),
|
|
||||||
rx.chakra.link(
|
|
||||||
rx.chakra.center(
|
|
||||||
rx.chakra.image(
|
|
||||||
src="/github.svg",
|
|
||||||
height="3em",
|
|
||||||
padding="0.5em",
|
|
||||||
),
|
|
||||||
box_shadow=box_shadow,
|
|
||||||
bg="transparent",
|
|
||||||
border_radius=border_radius,
|
|
||||||
_hover={
|
|
||||||
"bg": accent_color,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
href="https://github.com/reflex-dev/reflex",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
border_bottom=border,
|
|
||||||
padding="1em",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sidebar_footer() -> rx.Component:
|
|
||||||
"""Sidebar footer.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The sidebar footer component.
|
|
||||||
"""
|
|
||||||
return rx.chakra.hstack(
|
|
||||||
rx.chakra.link(
|
|
||||||
rx.chakra.center(
|
|
||||||
rx.chakra.image(
|
|
||||||
src="/paneleft.svg",
|
|
||||||
height="2em",
|
|
||||||
padding="0.5em",
|
|
||||||
),
|
|
||||||
bg="transparent",
|
|
||||||
border_radius=border_radius,
|
|
||||||
**hover_accent_bg,
|
|
||||||
),
|
|
||||||
on_click=State.toggle_sidebar_displayed,
|
|
||||||
transform=rx.cond(~State.sidebar_displayed, "rotate(180deg)", ""),
|
|
||||||
transition="transform 0.5s, left 0.5s",
|
|
||||||
position="relative",
|
|
||||||
left=rx.cond(State.sidebar_displayed, "0px", "20.5em"),
|
|
||||||
**overlapping_button_style,
|
|
||||||
),
|
|
||||||
rx.chakra.spacer(),
|
|
||||||
rx.chakra.link(
|
|
||||||
rx.chakra.text(
|
|
||||||
"Docs",
|
|
||||||
),
|
|
||||||
href="https://reflex.dev/docs/getting-started/introduction/",
|
|
||||||
),
|
|
||||||
rx.chakra.link(
|
|
||||||
rx.chakra.text(
|
|
||||||
"Blog",
|
|
||||||
),
|
|
||||||
href="https://reflex.dev/blog/",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
border_top=border,
|
|
||||||
padding="1em",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sidebar_item(text: str, icon: str, url: str) -> rx.Component:
|
|
||||||
"""Sidebar item.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): The text of the item.
|
|
||||||
icon (str): The icon of the item.
|
|
||||||
url (str): The URL of the item.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The sidebar item component.
|
|
||||||
"""
|
|
||||||
return rx.chakra.link(
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.image(
|
|
||||||
src=icon,
|
|
||||||
height="2.5em",
|
|
||||||
padding="0.5em",
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
text,
|
|
||||||
),
|
|
||||||
bg=rx.cond(
|
|
||||||
State.origin_url == f"/{text.lower()}/",
|
|
||||||
accent_color,
|
|
||||||
"transparent",
|
|
||||||
),
|
|
||||||
color=rx.cond(
|
|
||||||
State.origin_url == f"/{text.lower()}/",
|
|
||||||
accent_text_color,
|
|
||||||
text_color,
|
|
||||||
),
|
|
||||||
border_radius=border_radius,
|
|
||||||
box_shadow=box_shadow,
|
|
||||||
width="100%",
|
|
||||||
padding_x="1em",
|
|
||||||
),
|
|
||||||
href=url,
|
|
||||||
width="100%",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sidebar() -> rx.Component:
|
|
||||||
"""Sidebar.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
rx.Component: The sidebar component.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
sidebar_header(),
|
|
||||||
rx.chakra.vstack(
|
|
||||||
sidebar_item(
|
|
||||||
"Welcome",
|
|
||||||
"/github.svg",
|
|
||||||
"/",
|
|
||||||
),
|
|
||||||
sidebar_item(
|
|
||||||
"Graphing Demo",
|
|
||||||
"/github.svg",
|
|
||||||
"/graphing",
|
|
||||||
),
|
|
||||||
sidebar_item(
|
|
||||||
"Data Table Demo",
|
|
||||||
"/github.svg",
|
|
||||||
"/datatable",
|
|
||||||
),
|
|
||||||
sidebar_item(
|
|
||||||
"Forms Demo",
|
|
||||||
"/github.svg",
|
|
||||||
"/forms",
|
|
||||||
),
|
|
||||||
sidebar_item(
|
|
||||||
"Chat App Demo",
|
|
||||||
"/github.svg",
|
|
||||||
"/chatapp",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
overflow_y="auto",
|
|
||||||
align_items="flex-start",
|
|
||||||
padding="1em",
|
|
||||||
),
|
|
||||||
rx.chakra.spacer(),
|
|
||||||
sidebar_footer(),
|
|
||||||
height="100dvh",
|
|
||||||
),
|
|
||||||
display=["none", "none", "block"],
|
|
||||||
min_width=sidebar_width,
|
|
||||||
height="100%",
|
|
||||||
position="sticky",
|
|
||||||
top="0px",
|
|
||||||
border_right=border,
|
|
||||||
)
|
|
@ -1,22 +0,0 @@
|
|||||||
"""Base state for the app."""
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
|
||||||
"""State for the app."""
|
|
||||||
|
|
||||||
sidebar_displayed: bool = True
|
|
||||||
|
|
||||||
@rx.var
|
|
||||||
def origin_url(self) -> str:
|
|
||||||
"""Get the url of the current page.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The url of the current page.
|
|
||||||
"""
|
|
||||||
return self.router_data.get("asPath", "")
|
|
||||||
|
|
||||||
def toggle_sidebar_displayed(self) -> None:
|
|
||||||
"""Toggle the sidebar displayed."""
|
|
||||||
self.sidebar_displayed = not self.sidebar_displayed
|
|
@ -1,40 +0,0 @@
|
|||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ..state import State
|
|
||||||
|
|
||||||
|
|
||||||
class FormState(State):
|
|
||||||
"""Form state."""
|
|
||||||
|
|
||||||
form_data: dict = {}
|
|
||||||
|
|
||||||
def handle_submit(self, form_data: dict):
|
|
||||||
"""Handle the form submit.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
form_data: The form data.
|
|
||||||
"""
|
|
||||||
self.form_data = form_data
|
|
||||||
|
|
||||||
|
|
||||||
class UploadState(State):
|
|
||||||
"""The app state."""
|
|
||||||
|
|
||||||
# The images to show.
|
|
||||||
img: list[str]
|
|
||||||
|
|
||||||
async def handle_upload(self, files: list[rx.UploadFile]):
|
|
||||||
"""Handle the upload of file(s).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
files: The uploaded files.
|
|
||||||
"""
|
|
||||||
for file in files:
|
|
||||||
upload_data = await file.read()
|
|
||||||
outfile = rx.get_asset_path(file.filename)
|
|
||||||
# Save the file.
|
|
||||||
with open(outfile, "wb") as file_object:
|
|
||||||
file_object.write(upload_data)
|
|
||||||
|
|
||||||
# Update the img var.
|
|
||||||
self.img.append(f"/{file.filename}")
|
|
@ -1,47 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ..state import State
|
|
||||||
|
|
||||||
|
|
||||||
class PieChartState(State):
|
|
||||||
"""Pie Chart State."""
|
|
||||||
|
|
||||||
resources: list[dict[str, Any]] = [
|
|
||||||
dict(type_="🏆", count=1),
|
|
||||||
dict(type_="🪵", count=1),
|
|
||||||
dict(type_="🥑", count=1),
|
|
||||||
dict(type_="🧱", count=1),
|
|
||||||
]
|
|
||||||
|
|
||||||
@rx.cached_var
|
|
||||||
def resource_types(self) -> list[str]:
|
|
||||||
"""Get the resource types.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The resource types.
|
|
||||||
"""
|
|
||||||
return [r["type_"] for r in self.resources]
|
|
||||||
|
|
||||||
def increment(self, type_: str):
|
|
||||||
"""Increment the count of a resource type.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
type_: The type of resource to increment.
|
|
||||||
"""
|
|
||||||
for resource in self.resources:
|
|
||||||
if resource["type_"] == type_:
|
|
||||||
resource["count"] += 1
|
|
||||||
break
|
|
||||||
|
|
||||||
def decrement(self, type_: str):
|
|
||||||
"""Decrement the count of a resource type.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
type_: The type of resource to decrement.
|
|
||||||
"""
|
|
||||||
for resource in self.resources:
|
|
||||||
if resource["type_"] == type_ and resource["count"] > 0:
|
|
||||||
resource["count"] -= 1
|
|
||||||
break
|
|
@ -1,67 +0,0 @@
|
|||||||
"""Styles for the app."""
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
from .state import State
|
|
||||||
|
|
||||||
border_radius = "0.375rem"
|
|
||||||
box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
|
|
||||||
border = "1px solid #F4F3F6"
|
|
||||||
text_color = "black"
|
|
||||||
accent_text_color = "#1A1060"
|
|
||||||
accent_color = "#F5EFFE"
|
|
||||||
hover_accent_color = {"_hover": {"color": accent_color}}
|
|
||||||
hover_accent_bg = {"_hover": {"bg": accent_color}}
|
|
||||||
content_width_vw = "90vw"
|
|
||||||
sidebar_width = "20em"
|
|
||||||
|
|
||||||
template_page_style = {
|
|
||||||
"padding_top": "5em",
|
|
||||||
"padding_x": "2em",
|
|
||||||
}
|
|
||||||
|
|
||||||
template_content_style = {
|
|
||||||
"width": rx.cond(
|
|
||||||
State.sidebar_displayed,
|
|
||||||
f"calc({content_width_vw} - {sidebar_width})",
|
|
||||||
content_width_vw,
|
|
||||||
),
|
|
||||||
"min-width": sidebar_width,
|
|
||||||
"align_items": "flex-start",
|
|
||||||
"box_shadow": box_shadow,
|
|
||||||
"border_radius": border_radius,
|
|
||||||
"padding": "1em",
|
|
||||||
"margin_bottom": "2em",
|
|
||||||
}
|
|
||||||
|
|
||||||
link_style = {
|
|
||||||
"color": text_color,
|
|
||||||
"text_decoration": "none",
|
|
||||||
**hover_accent_color,
|
|
||||||
}
|
|
||||||
|
|
||||||
overlapping_button_style = {
|
|
||||||
"background_color": "white",
|
|
||||||
"border": border,
|
|
||||||
"border_radius": border_radius,
|
|
||||||
}
|
|
||||||
|
|
||||||
base_style = {
|
|
||||||
rx.chakra.MenuButton: {
|
|
||||||
"width": "3em",
|
|
||||||
"height": "3em",
|
|
||||||
**overlapping_button_style,
|
|
||||||
},
|
|
||||||
rx.chakra.MenuItem: hover_accent_bg,
|
|
||||||
}
|
|
||||||
|
|
||||||
tab_style = {
|
|
||||||
"color": "#494369",
|
|
||||||
"font_weight": 600,
|
|
||||||
"_selected": {
|
|
||||||
"color": "#5646ED",
|
|
||||||
"bg": "#F5EFFE",
|
|
||||||
"padding_x": "0.5em",
|
|
||||||
"padding_y": "0.25em",
|
|
||||||
"border_radius": "8px",
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
from .loading_icon import loading_icon
|
|
||||||
from .modal import modal
|
|
||||||
from .navbar import navbar
|
|
||||||
from .sidebar import sidebar
|
|
@ -1,118 +0,0 @@
|
|||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ...webui import styles
|
|
||||||
from ...webui.components import loading_icon
|
|
||||||
from ...webui.state import QA, State
|
|
||||||
|
|
||||||
|
|
||||||
def message(qa: QA) -> rx.Component:
|
|
||||||
"""A single question/answer message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
qa: The question/answer pair.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A component displaying the question/answer pair.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.box(
|
|
||||||
rx.chakra.text(
|
|
||||||
qa.question,
|
|
||||||
bg=styles.border_color,
|
|
||||||
shadow=styles.shadow_light,
|
|
||||||
**styles.message_style,
|
|
||||||
),
|
|
||||||
text_align="right",
|
|
||||||
margin_top="1em",
|
|
||||||
),
|
|
||||||
rx.chakra.box(
|
|
||||||
rx.chakra.text(
|
|
||||||
qa.answer,
|
|
||||||
bg=styles.accent_color,
|
|
||||||
shadow=styles.shadow_light,
|
|
||||||
**styles.message_style,
|
|
||||||
),
|
|
||||||
text_align="left",
|
|
||||||
padding_top="1em",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def chat() -> rx.Component:
|
|
||||||
"""List all the messages in a single conversation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A component displaying all the messages in a single conversation.
|
|
||||||
"""
|
|
||||||
return rx.chakra.vstack(
|
|
||||||
rx.chakra.box(rx.foreach(State.chats[State.current_chat], message)),
|
|
||||||
py="8",
|
|
||||||
flex="1",
|
|
||||||
width="100%",
|
|
||||||
max_w="3xl",
|
|
||||||
padding_x="4",
|
|
||||||
align_self="center",
|
|
||||||
overflow="hidden",
|
|
||||||
padding_bottom="5em",
|
|
||||||
**styles.base_style[rx.chakra.Vstack],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def action_bar() -> rx.Component:
|
|
||||||
"""The action bar to send a new message.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The action bar to send a new message.
|
|
||||||
"""
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.vstack(
|
|
||||||
rx.chakra.form(
|
|
||||||
rx.chakra.form_control(
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.input(
|
|
||||||
placeholder="Type something...",
|
|
||||||
value=State.question,
|
|
||||||
on_change=State.set_question,
|
|
||||||
_placeholder={"color": "#fffa"},
|
|
||||||
_hover={"border_color": styles.accent_color},
|
|
||||||
style=styles.input_style,
|
|
||||||
),
|
|
||||||
rx.chakra.button(
|
|
||||||
rx.cond(
|
|
||||||
State.processing,
|
|
||||||
loading_icon(height="1em"),
|
|
||||||
rx.chakra.text("Send"),
|
|
||||||
),
|
|
||||||
type_="submit",
|
|
||||||
_hover={"bg": styles.accent_color},
|
|
||||||
style=styles.input_style,
|
|
||||||
),
|
|
||||||
**styles.base_style[rx.chakra.Hstack],
|
|
||||||
),
|
|
||||||
is_disabled=State.processing,
|
|
||||||
),
|
|
||||||
on_submit=State.process_question,
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
rx.chakra.text(
|
|
||||||
"ReflexGPT may return factually incorrect or misleading responses. Use discretion.",
|
|
||||||
font_size="xs",
|
|
||||||
color="#fff6",
|
|
||||||
text_align="center",
|
|
||||||
),
|
|
||||||
width="100%",
|
|
||||||
max_w="3xl",
|
|
||||||
mx="auto",
|
|
||||||
**styles.base_style[rx.chakra.Vstack],
|
|
||||||
),
|
|
||||||
position="sticky",
|
|
||||||
bottom="0",
|
|
||||||
left="0",
|
|
||||||
py="4",
|
|
||||||
backdrop_filter="auto",
|
|
||||||
backdrop_blur="lg",
|
|
||||||
border_top=f"1px solid {styles.border_color}",
|
|
||||||
align_items="stretch",
|
|
||||||
width="100%",
|
|
||||||
)
|
|
@ -1,26 +0,0 @@
|
|||||||
import reflex as rx
|
|
||||||
|
|
||||||
|
|
||||||
class LoadingIcon(rx.Component):
|
|
||||||
"""A custom loading icon component."""
|
|
||||||
|
|
||||||
library = "react-loading-icons"
|
|
||||||
tag = "SpinningCircles"
|
|
||||||
stroke: rx.Var[str]
|
|
||||||
stroke_opacity: rx.Var[str]
|
|
||||||
fill: rx.Var[str]
|
|
||||||
fill_opacity: rx.Var[str]
|
|
||||||
stroke_width: rx.Var[str]
|
|
||||||
speed: rx.Var[str]
|
|
||||||
height: rx.Var[str]
|
|
||||||
|
|
||||||
def get_event_triggers(self) -> dict:
|
|
||||||
"""Get the event triggers that pass the component's value to the handler.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A dict mapping the event trigger to the var that is passed to the handler.
|
|
||||||
"""
|
|
||||||
return {"on_change": lambda status: [status]}
|
|
||||||
|
|
||||||
|
|
||||||
loading_icon = LoadingIcon.create
|
|
@ -1,56 +0,0 @@
|
|||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ...webui.state import State
|
|
||||||
|
|
||||||
|
|
||||||
def modal() -> rx.Component:
|
|
||||||
"""A modal to create a new chat.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The modal component.
|
|
||||||
"""
|
|
||||||
return rx.chakra.modal(
|
|
||||||
rx.chakra.modal_overlay(
|
|
||||||
rx.chakra.modal_content(
|
|
||||||
rx.chakra.modal_header(
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.text("Create new chat"),
|
|
||||||
rx.chakra.icon(
|
|
||||||
tag="close",
|
|
||||||
font_size="sm",
|
|
||||||
on_click=State.toggle_modal,
|
|
||||||
color="#fff8",
|
|
||||||
_hover={"color": "#fff"},
|
|
||||||
cursor="pointer",
|
|
||||||
),
|
|
||||||
align_items="center",
|
|
||||||
justify_content="space-between",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
rx.chakra.modal_body(
|
|
||||||
rx.chakra.input(
|
|
||||||
placeholder="Type something...",
|
|
||||||
on_blur=State.set_new_chat_name,
|
|
||||||
bg="#222",
|
|
||||||
border_color="#fff3",
|
|
||||||
_placeholder={"color": "#fffa"},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.modal_footer(
|
|
||||||
rx.chakra.button(
|
|
||||||
"Create",
|
|
||||||
bg="#5535d4",
|
|
||||||
box_shadow="md",
|
|
||||||
px="4",
|
|
||||||
py="2",
|
|
||||||
h="auto",
|
|
||||||
_hover={"bg": "#4c2db3"},
|
|
||||||
on_click=State.create_chat,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bg="#222",
|
|
||||||
color="#fff",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
is_open=State.modal_open,
|
|
||||||
)
|
|
@ -1,70 +0,0 @@
|
|||||||
import reflex as rx
|
|
||||||
|
|
||||||
from ...webui import styles
|
|
||||||
from ...webui.state import State
|
|
||||||
|
|
||||||
|
|
||||||
def navbar():
|
|
||||||
return rx.chakra.box(
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.icon(
|
|
||||||
tag="hamburger",
|
|
||||||
mr=4,
|
|
||||||
on_click=State.toggle_drawer,
|
|
||||||
cursor="pointer",
|
|
||||||
),
|
|
||||||
rx.chakra.link(
|
|
||||||
rx.chakra.box(
|
|
||||||
rx.chakra.image(src="favicon.ico", width=30, height="auto"),
|
|
||||||
p="1",
|
|
||||||
border_radius="6",
|
|
||||||
bg="#F0F0F0",
|
|
||||||
mr="2",
|
|
||||||
),
|
|
||||||
href="/",
|
|
||||||
),
|
|
||||||
rx.chakra.breadcrumb(
|
|
||||||
rx.chakra.breadcrumb_item(
|
|
||||||
rx.chakra.heading("ReflexGPT", size="sm"),
|
|
||||||
),
|
|
||||||
rx.chakra.breadcrumb_item(
|
|
||||||
rx.chakra.text(
|
|
||||||
State.current_chat, size="sm", font_weight="normal"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rx.chakra.hstack(
|
|
||||||
rx.chakra.button(
|
|
||||||
"+ New chat",
|
|
||||||
bg=styles.accent_color,
|
|
||||||
px="4",
|
|
||||||
py="2",
|
|
||||||
h="auto",
|
|
||||||
on_click=State.toggle_modal,
|
|
||||||
),
|
|
||||||
rx.chakra.menu(
|
|
||||||
rx.chakra.menu_button(
|
|
||||||
rx.chakra.avatar(name="User", size="md"),
|
|
||||||
rx.chakra.box(),
|
|
||||||
),
|
|
||||||
rx.chakra.menu_list(
|
|
||||||
rx.chakra.menu_item("Help"),
|
|
||||||
rx.chakra.menu_divider(),
|
|
||||||
rx.chakra.menu_item("Settings"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
spacing="8",
|
|
||||||
),
|
|
||||||
justify="space-between",
|
|
||||||
),
|
|
||||||
bg=styles.bg_dark_color,
|
|
||||||
backdrop_filter="auto",
|
|
||||||
backdrop_blur="lg",
|
|
||||||
p="4",
|
|
||||||
border_bottom=f"1px solid {styles.border_color}",
|
|
||||||
position="sticky",
|
|
||||||
top="0",
|
|
||||||
z_index="100",
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user