mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Compare commits
1660 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b333ffa781 | |||
|
|
8f5cc6174c | ||
|
|
29d014a0e1 | ||
|
|
396a0ca563 | ||
|
|
a500178b3c | ||
|
|
7d770f55fb | ||
|
|
db283a66e8 | ||
|
|
c642aef8ca | ||
|
|
396df1a506 | ||
|
|
9c9fa7e537 | ||
|
|
86e2eb0648 | ||
|
|
491db2f0c6 | ||
|
|
f0fb375028 | ||
|
|
16d8bab61a | ||
|
|
2d83a67bd6 | ||
|
|
5ad7e97e05 | ||
|
|
b7a7b6b923 | ||
|
|
0e00d2328d | ||
|
|
53db70e784 | ||
|
|
76c699b4ba | ||
|
|
c901bc07a4 | ||
|
|
b7db23bbae | ||
| 308b95ec13 | |||
| 5f77ce5b4f | |||
| 4da57d42de | |||
| 2777588bb4 | |||
|
|
389b20d977 | ||
|
|
d06459fa49 | ||
|
|
e2a55cbf34 | ||
|
|
a5e6ade9cb | ||
|
|
a1e32566d3 | ||
|
|
8c7bfb3e1a | ||
|
|
bb0480e978 | ||
|
|
2ccc745513 | ||
|
|
bea83fe94d | ||
|
|
3feaf689d8 | ||
|
|
bd627b58af | ||
|
|
c561d33cb2 | ||
|
|
c8fd3bd683 | ||
|
|
fef1e31634 | ||
|
|
1abaf87abe | ||
|
|
38593fbd85 | ||
|
|
8d187fd275 | ||
|
|
646cc81656 | ||
|
|
01f7536b36 | ||
|
|
97e5ec02f8 | ||
|
|
3dced01af0 | ||
| 0cf4534c5c | |||
| 044f66fba3 | |||
| 40a9ddad4e | |||
|
|
848db340da | ||
|
|
c6da4ffcdd | ||
|
|
8ac7e34be2 | ||
|
|
e4be882994 | ||
|
|
adc05e663f | ||
|
|
0bc236ac98 | ||
| da3d3d9296 | |||
| 6a6af6a952 | |||
| 582a4ab267 | |||
| ebde8546c9 | |||
| fffd0f5465 | |||
|
|
3b6db8e21a | ||
|
|
19a0b7dd76 | ||
|
|
9647ebe206 | ||
|
|
0c726a1275 | ||
|
|
f71eb55f1f | ||
|
|
edcc0b7dbc | ||
|
|
4910b308ee | ||
|
|
0cc1c323ed | ||
|
|
24fd19b35d | ||
|
|
fbd0c7a99e | ||
| 4f5b26698c | |||
| d887e5c24c | |||
| 0c69391bbd | |||
| 91598f9121 | |||
|
|
98d09e7816 | ||
| 7ae9432524 | |||
|
|
c883f0ad8a | ||
|
|
eae60113af | ||
|
|
1aab5240cf | ||
|
|
edb7abde5a | ||
|
|
29d95340b0 | ||
|
|
6fb19ac74f | ||
|
|
37dc28a69d | ||
|
|
7623668256 | ||
|
|
bbf7aacd4d | ||
|
|
d6c1e08ef4 | ||
|
|
d1f95f04a7 | ||
|
|
0dacf8a326 | ||
|
|
e534b10722 | ||
|
|
0785cc5aa9 | ||
|
|
48025e0102 | ||
|
|
eeb719e8f7 | ||
|
|
7933edad43 | ||
|
|
5f75728ede | ||
|
|
8cff0ed6c2 | ||
|
|
a8964f4108 | ||
|
|
740aeedca3 | ||
|
|
8152e7e62c | ||
|
|
b36c95b91e | ||
|
|
b5c1fd95dc | ||
|
|
23f7cbf1c3 | ||
|
|
ae78ecc2c5 | ||
|
|
dd06516d1c | ||
|
|
8066f36a4e | ||
|
|
3bbf6b9d53 | ||
|
|
8bc5944178 | ||
|
|
a13b99642b | ||
|
|
060db5983c | ||
|
|
42bba6920e | ||
|
|
ea11d78995 | ||
|
|
fe63ca762f | ||
|
|
a82982725e | ||
|
|
7dfb760421 | ||
|
|
b6e9a3f37e | ||
|
|
3f28309b7b | ||
|
|
d2a4a330f9 | ||
|
|
d8c35770ab | ||
|
|
dbb6794a41 | ||
|
|
63c9909aa0 | ||
|
|
f1dac5087e | ||
|
|
4f80d7013e | ||
|
|
2919df1df5 | ||
|
|
a2df2f014b | ||
|
|
8673169ee7 | ||
|
|
2c2e0fb172 | ||
|
|
d45c5de906 | ||
|
|
eb985beed6 | ||
|
|
ff9aa0fc54 | ||
|
|
0bd1a3efb2 | ||
|
|
08e7ec948b | ||
|
|
6092c07eb5 | ||
|
|
09d74336fa | ||
|
|
694354e5e6 | ||
|
|
55f04db6b8 | ||
|
|
6f8360a878 | ||
|
|
6ca4685fee | ||
|
|
d86b592add | ||
|
|
9944b3dac1 | ||
|
|
8ed79bd1af | ||
|
|
0600ae6213 | ||
|
|
623415d843 | ||
|
|
87b84a83cc | ||
|
|
aa5eba9723 | ||
|
|
c2cd883f93 | ||
|
|
43e20a8cd9 | ||
|
|
48299cdd7f | ||
|
|
2335ca0f72 | ||
|
|
e42c1a33b5 | ||
|
|
b1a38c2d50 | ||
|
|
f1f05303db | ||
|
|
8424a733a2 | ||
|
|
2bf7a4a08c | ||
|
|
b4bae8173b | ||
|
|
3c10888e3a | ||
|
|
1bd143027a | ||
|
|
18d31f2d5b | ||
|
|
e142d655b9 | ||
|
|
e1ce1f66cd | ||
|
|
08c9a4d86b | ||
|
|
a620b38ba0 | ||
|
|
8e3c0cd658 | ||
|
|
f709a2805d | ||
|
|
adbadfc4f5 | ||
|
|
9c7a9cbace | ||
|
|
1333688a4e | ||
|
|
17852b78d5 | ||
|
|
dd15ae5560 | ||
|
|
9d33990199 | ||
|
|
866e7cf371 | ||
|
|
f5c1596ddf | ||
|
|
64cff172a1 | ||
|
|
c10b99b29a | ||
|
|
bca90f191c | ||
|
|
7996a10116 | ||
|
|
db4562f67d | ||
|
|
3fd0382a51 | ||
|
|
3e98e5e86c | ||
|
|
7b17965b26 | ||
|
|
5c050d5779 | ||
|
|
5d6d1a6da7 | ||
|
|
b59d876e76 | ||
|
|
9028aede6e | ||
|
|
aa0353a501 | ||
|
|
63c3f31c99 | ||
|
|
2fe44af8ba | ||
|
|
515f0bf9c2 | ||
|
|
27a52a7962 | ||
|
|
61feb43aba | ||
|
|
07fdcb6160 | ||
|
|
ac08f4447f | ||
|
|
d243c90d0a | ||
|
|
72033ab689 | ||
|
|
3f63501fa8 | ||
|
|
019496e3a8 | ||
|
|
52695a9ece | ||
|
|
a20ce951e7 | ||
|
|
85fa157d5a | ||
|
|
1068219c56 | ||
|
|
427ec50624 | ||
|
|
52ce1509a5 | ||
|
|
fe06583643 | ||
|
|
3e3c69b5bc | ||
|
|
3b0802535d | ||
|
|
75415090bd | ||
|
|
6f664cb05a | ||
|
|
a8ea67178d | ||
|
|
64a464aefb | ||
|
|
67885ad9fa | ||
|
|
e5bf72e897 | ||
|
|
1f62190eef | ||
|
|
f839e6cc79 | ||
|
|
52aad1ec08 | ||
|
|
5f0d1252b6 | ||
|
|
3370f0f23d | ||
|
|
e1574b1485 | ||
|
|
78fae736f9 | ||
|
|
ed3811a65c | ||
|
|
4e915de35d | ||
|
|
94e6648ee5 | ||
|
|
75a985e63e | ||
|
|
5714558785 | ||
|
|
c3ccb5b7bf | ||
|
|
87849cda4f | ||
|
|
f62114c7eb | ||
|
|
24f57dde2f | ||
|
|
cdfcd8a4c9 | ||
|
|
1bc1370701 | ||
|
|
6b7b080827 | ||
|
|
3556e1320d | ||
|
|
621640ca71 | ||
|
|
2c0e439d0d | ||
|
|
a392d8425e | ||
|
|
0273d78de9 | ||
|
|
ed51d8dcf6 | ||
|
|
3d78ca8b09 | ||
|
|
1cec856c63 | ||
|
|
6212c81ec6 | ||
|
|
408459b883 | ||
|
|
f6d88042b5 | ||
|
|
b58bdc9e32 | ||
|
|
f3501f44cb | ||
|
|
61f37b10e2 | ||
|
|
2856aff757 | ||
|
|
3949ecc054 | ||
|
|
acd9ea57d7 | ||
|
|
794a2a1892 | ||
|
|
af8bed6d3f | ||
|
|
462f54f906 | ||
|
|
1c4e99cf2c | ||
|
|
a349b9bdfe | ||
|
|
75e790836a | ||
|
|
5ba677cd36 | ||
|
|
a7b761a89c | ||
|
|
6e0b00d60c | ||
|
|
c107a938be | ||
|
|
c6fc9cee8e | ||
|
|
99a4bf5eb5 | ||
|
|
46410ff933 | ||
|
|
75e868e71e | ||
|
|
53b1b4d682 | ||
|
|
566d9bee5c | ||
|
|
b8bf65d68e | ||
|
|
075c69a3cd | ||
|
|
ada10067dd | ||
|
|
d0f680b379 | ||
|
|
ca2c1d0b48 | ||
|
|
5fd5939395 | ||
|
|
e5ca631b52 | ||
|
|
3313fdeb32 | ||
|
|
2bd8ff0b6c | ||
|
|
5c9d6d455d | ||
|
|
8f6cf6fef7 | ||
|
|
a5f8d42d34 | ||
|
|
153ec2823d | ||
|
|
9f65a30504 | ||
|
|
b018123f19 | ||
|
|
97e3e969e4 | ||
|
|
84099514e6 | ||
|
|
3286e683e6 | ||
|
|
be6ea2a7b8 | ||
|
|
4a939141f6 | ||
|
|
1034477f1e | ||
|
|
49cfcd817d | ||
|
|
39822d7e57 | ||
|
|
142d333c5c | ||
|
|
13c491712b | ||
|
|
5c527c2a28 | ||
|
|
41979f0823 | ||
|
|
192b0a8fb5 | ||
|
|
6a7be1e142 | ||
|
|
b3666060d4 | ||
|
|
c9c07a6bbc | ||
|
|
d57287b89b | ||
|
|
79e7015a32 | ||
|
|
6c4ee951e0 | ||
|
|
f0f4633346 | ||
|
|
6c37e8f4e7 | ||
|
|
4d05416ce3 | ||
|
|
60993b9d23 | ||
|
|
959e7c11ce | ||
|
|
21ae739eec | ||
|
|
b1c3c9a9d6 | ||
|
|
bf985c8ac6 | ||
|
|
21c5eae717 | ||
|
|
4d5cf249a0 | ||
|
|
8c7b811135 | ||
|
|
940b879950 | ||
|
|
ccf650d4ca | ||
|
|
6342f16eb5 | ||
|
|
8816cd41d5 | ||
|
|
4b190eb412 | ||
|
|
9f24841f6d | ||
|
|
a0b6c36928 | ||
|
|
3f691de7a3 | ||
|
|
247572863a | ||
|
|
5a5ac35f36 | ||
|
|
220594f263 | ||
|
|
f541ca9dda | ||
|
|
6a64af1066 | ||
|
|
35141266c1 | ||
|
|
6adb72727e | ||
|
|
6eed8f42c7 | ||
|
|
ae72316629 | ||
|
|
90ab0e33a2 | ||
|
|
9f7eb08fca | ||
|
|
6a53ec3c5d | ||
|
|
c5550cd466 | ||
|
|
f6569313d7 | ||
|
|
880036c0ae | ||
|
|
da53eeea57 | ||
|
|
e349b0f18b | ||
|
|
34ffa93945 | ||
|
|
7be6308248 | ||
|
|
57e7f4db95 | ||
|
|
f5fbb5b06f | ||
|
|
d9375b9fe1 | ||
|
|
1e3d57817c | ||
|
|
ebe555203a | ||
|
|
280337a305 | ||
|
|
02cec859e5 | ||
|
|
1dd9a2fbe4 | ||
|
|
df363c0ba7 | ||
|
|
90724b32ec | ||
|
|
0a24e106ba | ||
|
|
e6c73883ea | ||
|
|
e315077630 | ||
|
|
85c427b842 | ||
|
|
5ee5531f32 | ||
|
|
3737f2a091 | ||
|
|
ac78517044 | ||
|
|
12601d0b44 | ||
|
|
b3a606d9df | ||
|
|
426e582ba0 | ||
|
|
92c8b371ae | ||
|
|
d8f2bd04ac | ||
|
|
855fa1411f | ||
|
|
4e7b3bc8f2 | ||
|
|
83d1ad8a2c | ||
|
|
7f02324dce | ||
|
|
d453026d19 | ||
|
|
2fde8e91e5 | ||
|
|
23ebbd021b | ||
|
|
9b974bda0d | ||
|
|
de7e4e49da | ||
|
|
ead42beff6 | ||
|
|
35229721ea | ||
|
|
2f8e5189d3 | ||
|
|
4c8cd67db9 | ||
|
|
54247df801 | ||
|
|
e4be47a08b | ||
|
|
d2bf69e354 | ||
|
|
88ee64d585 | ||
|
|
d0c827ed38 | ||
|
|
d26766b7bc | ||
|
|
e6c6f966f7 | ||
|
|
407f251866 | ||
|
|
6cd9e54427 | ||
|
|
c9ec4507ad | ||
|
|
9481df23e1 | ||
|
|
0bd8c2504c | ||
|
|
bcb35919a4 | ||
|
|
5dc6d5582a | ||
|
|
a8ab7dd388 | ||
|
|
7c722c23a5 | ||
|
|
83b8c3a3fc | ||
|
|
36a36b200d | ||
|
|
d297e73a55 | ||
|
|
572053df82 | ||
|
|
def5661728 | ||
|
|
078cd0d88c | ||
|
|
d061837467 | ||
|
|
defc3ac7f1 | ||
|
|
97a0705d2e | ||
|
|
a88c2fe000 | ||
|
|
2a62ef7344 | ||
|
|
258342e1ca | ||
|
|
69e8e4be43 | ||
|
|
1c93c1630b | ||
|
|
ec9f2cedd1 | ||
|
|
10055772d8 | ||
|
|
61e40de32b | ||
|
|
074d228a7a | ||
|
|
bf461cdebc | ||
|
|
ffc52ef87c | ||
|
|
ea1f72e92d | ||
|
|
9693ad9b11 | ||
|
|
bdf228eb42 | ||
|
|
c4b1cc69be | ||
|
|
36b9219e32 | ||
|
|
43d7c71a68 | ||
|
|
3eda1cf3b4 | ||
|
|
6917b4c2ca | ||
|
|
21fc2059b7 | ||
|
|
10e51ba689 | ||
|
|
c93ea30b3b | ||
|
|
118a00c012 | ||
|
|
f9b2d10c71 | ||
|
|
2fe129252f | ||
|
|
17e1152de6 | ||
|
|
454aa4b654 | ||
|
|
169368b7f0 | ||
|
|
41bdcfe221 | ||
|
|
003c45dbff | ||
|
|
9378497346 | ||
|
|
99ed744c57 | ||
|
|
a9ed7de28d | ||
|
|
39c0106e87 | ||
|
|
9070e12e0d | ||
|
|
d73cc5f58c | ||
|
|
a777d22a53 | ||
|
|
aaae566231 | ||
|
|
28f0fa48a4 | ||
|
|
1983138c91 | ||
|
|
9cc571be95 | ||
|
|
602015fca1 | ||
|
|
1ab133dae8 | ||
|
|
317f449454 | ||
|
|
32f662ae80 | ||
|
|
7236109d75 | ||
|
|
506c8a633e | ||
|
|
a309cb5d2c | ||
|
|
5001b2e572 | ||
|
|
756088e7fb | ||
|
|
4729ca3af0 | ||
|
|
cccfb08835 | ||
|
|
d01909a524 | ||
|
|
ee128eac7c | ||
|
|
6df9732965 | ||
|
|
8a84a479f1 | ||
|
|
7513017e21 | ||
|
|
adf0788895 | ||
|
|
fc91cd8bc7 | ||
|
|
459cad8407 | ||
|
|
863a5b5a49 | ||
|
|
1c6336b350 | ||
|
|
fb12a4c219 | ||
|
|
16a3edd432 | ||
|
|
ebc8f60e21 | ||
|
|
727f97fd48 | ||
|
|
de626b8627 | ||
|
|
f519ffdb18 | ||
|
|
57f9478d16 | ||
|
|
5700fa3953 | ||
|
|
44438bffbd | ||
|
|
dd0c393b45 | ||
|
|
a00a387735 | ||
|
|
b4ee044fa6 | ||
|
|
df98264dd0 | ||
|
|
b6c2179893 | ||
|
|
b5176fdbc0 | ||
|
|
79231ee399 | ||
|
|
c0f6a2f8c3 | ||
|
|
99531514c8 | ||
|
|
0fab6e6063 | ||
|
|
784b5f1b1c | ||
|
|
3945e9a4ed | ||
|
|
ad3f0aecaf | ||
|
|
1d76b762c7 | ||
|
|
f7c7398ba8 | ||
|
|
186eac5095 | ||
|
|
43ede611e3 | ||
|
|
d5e4af7a4b | ||
|
|
f1991c25bc | ||
|
|
c4e2d6e379 | ||
|
|
b11d554c11 | ||
|
|
b05d5920ab | ||
|
|
926c4cef27 | ||
|
|
5c6b4cbd3c | ||
|
|
b93199f007 | ||
|
|
07f013dae2 | ||
|
|
fa3eae677d | ||
|
|
c5364ca157 | ||
|
|
c28cca97d1 | ||
|
|
9a61716f74 | ||
|
|
700f2b9c12 | ||
|
|
999dcbdd1b | ||
|
|
06bb68a6c6 | ||
|
|
dc4f6994fb | ||
|
|
8fc2c39d62 | ||
|
|
4d6a180638 | ||
|
|
460e9833f0 | ||
|
|
c8256c5450 | ||
|
|
e06f5ccfe4 | ||
|
|
68dc5ca052 | ||
|
|
ac21576ab9 | ||
|
|
b0ebe58636 | ||
|
|
7833fd9b12 | ||
|
|
81208637f5 | ||
|
|
5303b33c8b | ||
|
|
f54b80f88c | ||
|
|
068249196f | ||
|
|
87cf891e50 | ||
|
|
6d23daa480 | ||
|
|
ee9f6ae079 | ||
|
|
488bac7413 | ||
|
|
8ff0872d41 | ||
|
|
a96926cd77 | ||
|
|
06b9b4938d | ||
|
|
b5e21ab136 | ||
|
|
fe617431f4 | ||
|
|
99d992f708 | ||
|
|
f682d184fb | ||
|
|
2bfbfd7a03 | ||
|
|
c32369bd27 | ||
|
|
b8f22e2967 | ||
|
|
b4b71e5a11 | ||
|
|
4dacbc51e2 | ||
|
|
af884cb284 | ||
|
|
a75f26b922 | ||
|
|
d32cb7efdd | ||
|
|
03576615e4 | ||
|
|
bda7c5cf06 | ||
|
|
0e72ba8819 | ||
|
|
4e6172b99d | ||
|
|
9241e2e5d5 | ||
|
|
830389f62c | ||
|
|
2c3dbb5eed | ||
|
|
f1b36233b6 | ||
|
|
eb83b729b2 | ||
|
|
b69e6a910d | ||
|
|
b9ee0b6b7a | ||
|
|
f8365ca6c3 | ||
|
|
c2f159b24a | ||
|
|
54d5869457 | ||
|
|
285ba765a7 | ||
|
|
91c0c8b002 | ||
|
|
4a61eba58e | ||
|
|
58df09b492 | ||
|
|
6313e4c9fb | ||
|
|
0f3a9311e0 | ||
|
|
987d50c092 | ||
|
|
2fa88f94b6 | ||
|
|
7022512b83 | ||
|
|
4135da42ac | ||
|
|
d975c51b96 | ||
|
|
23bf5c42ca | ||
|
|
c3a8d5a9b5 | ||
|
|
0d0139b322 | ||
|
|
5384a4766a | ||
|
|
a12233e788 | ||
|
|
80e7186aa9 | ||
|
|
9ea4baa41c | ||
|
|
0a76a9b115 | ||
|
|
fd98ab2084 | ||
|
|
48d4c22362 | ||
|
|
10e4f715a5 | ||
|
|
9031a0ac9f | ||
|
|
37ce45fffb | ||
|
|
39169de63a | ||
|
|
21cff29c31 | ||
|
|
589b3eb7c3 | ||
|
|
1208416b92 | ||
|
|
e5cea3ad37 | ||
|
|
8c11992e59 | ||
|
|
b864e5da1f | ||
|
|
77939d2ca5 | ||
|
|
9cf18a8bdc | ||
|
|
c25aa1add4 | ||
|
|
3900a086b8 | ||
|
|
64c66e00d6 | ||
|
|
4ba2892168 | ||
|
|
aee68d20bb | ||
|
|
d4be55c2ea | ||
|
|
e2f7d5b2f9 | ||
|
|
9417eec81e | ||
|
|
d056201e5b | ||
|
|
93d731cc4b | ||
|
|
d24013d582 | ||
|
|
31f0d9b282 | ||
|
|
f7d4413c62 | ||
|
|
3131d56298 | ||
|
|
9c2355117c | ||
|
|
94029386ae | ||
|
|
0fbe57f96b | ||
|
|
5f6059ef73 | ||
|
|
ba8d7b541f | ||
|
|
b1238ab4eb | ||
|
|
287f89aa04 | ||
|
|
d5a62848dd | ||
|
|
03f954408a | ||
|
|
d4d362a9ca | ||
|
|
aea4d509c9 | ||
|
|
bdb0c8f645 | ||
|
|
c28cb3941f | ||
|
|
d66f4d5315 | ||
|
|
b088787f7b | ||
|
|
6363940d6c | ||
|
|
a58ede99f2 | ||
|
|
7e66db0d43 | ||
|
|
179de9fb67 | ||
|
|
500b0024f0 | ||
|
|
4bec66e1c6 | ||
|
|
1cdc5d3294 | ||
|
|
3d2c51962b | ||
|
|
5c81649b19 | ||
|
|
d64d33c809 | ||
|
|
b875057ae2 | ||
|
|
2e5b0330c7 | ||
|
|
c7d3358582 | ||
|
|
1df0a44958 | ||
|
|
3c8fb93ab4 | ||
|
|
95ef76f7a0 | ||
|
|
9953597bb6 | ||
|
|
91e206faa1 | ||
|
|
06ed5cc8cf | ||
|
|
483dfcdd2c | ||
|
|
a4330c3558 | ||
|
|
a6a92128f0 | ||
|
|
436be2b96a | ||
|
|
89323dadc2 | ||
|
|
7e3183ef16 | ||
|
|
854d3e4d80 | ||
|
|
3150b4ded6 | ||
|
|
1be262db40 | ||
|
|
3d76e1e1db | ||
|
|
8af4c5fd7b | ||
|
|
8dbf7b27ef | ||
|
|
2b4f607cbc | ||
|
|
cbaed46fb4 | ||
|
|
9cab670bf8 | ||
|
|
6fb15535ed | ||
|
|
4721b7df92 | ||
|
|
06c6244599 | ||
|
|
3a2a1d1b74 | ||
|
|
531f4babff | ||
|
|
0f2ea6a1f3 | ||
|
|
7069a1de0a | ||
|
|
6f8f42b06a | ||
|
|
8db7edb6d9 | ||
|
|
de0d323ad3 | ||
|
|
01486830e3 | ||
|
|
7a3f965396 | ||
|
|
058f8c5500 | ||
|
|
0e24cf48c6 | ||
|
|
e864e26592 | ||
|
|
59df92d700 | ||
|
|
283a8046aa | ||
|
|
9ba6027599 | ||
|
|
8312831bc9 | ||
|
|
2786831d32 | ||
|
|
7366a41b1c | ||
|
|
2b43d4817c | ||
|
|
050f0bd0ad | ||
|
|
7c56313d2b | ||
|
|
ab3862d7f4 | ||
|
|
f4b54082cf | ||
|
|
ff7055749c | ||
|
|
a1d9c649f6 | ||
|
|
1aeaf4afef | ||
|
|
5d3e12fca4 | ||
|
|
3ae6ce5216 | ||
|
|
f5fc30ae63 | ||
|
|
7a6000d181 | ||
|
|
15fdf1ee5b | ||
|
|
4c2ab63185 | ||
|
|
0a10e6cbea | ||
|
|
935e35bb40 | ||
|
|
698134caab | ||
|
|
f12875027c | ||
|
|
833371918d | ||
|
|
a5f08d77a7 | ||
|
|
f3e4d34092 | ||
|
|
a43a8c546e | ||
|
|
b85e134477 | ||
|
|
4064c84cb4 | ||
|
|
dc569e9921 | ||
|
|
5337dedadf | ||
|
|
f1b261b4f8 | ||
|
|
a5ed4070bf | ||
|
|
fa30d33c9b | ||
|
|
3d9fb36cde | ||
|
|
6603c46b41 | ||
|
|
c7025f70d5 | ||
|
|
d03c7267d6 | ||
|
|
0af106c85c | ||
|
|
da15bb282e | ||
|
|
ff808ad313 | ||
|
|
0897385a30 | ||
|
|
bac1cdc55f | ||
|
|
91b9ed17c2 | ||
|
|
f2cfa5f0a7 | ||
|
|
306943a9f5 | ||
|
|
ff5076b12c | ||
|
|
cea23d2b07 | ||
|
|
1dd5e84c9a | ||
|
|
cfbdf7f924 | ||
|
|
2080cc2067 | ||
|
|
9cc8f2dfa9 | ||
|
|
2315ce956e | ||
|
|
e73603945d | ||
|
|
98eef0804e | ||
|
|
fa79055821 | ||
|
|
4a099ab6bc | ||
|
|
9f58921a6f | ||
|
|
9b9dd1100a | ||
|
|
2abf21b134 | ||
|
|
f4b9bdfb29 | ||
|
|
429d1d1cce | ||
|
|
9d44f93192 | ||
|
|
b8bca5a148 | ||
|
|
8c1d923f3e | ||
|
|
04749c13f4 | ||
|
|
e2ccb48463 | ||
|
|
02840593bc | ||
|
|
8503e0de90 | ||
|
|
23fee2737c | ||
|
|
0a94e1393a | ||
|
|
9287169201 | ||
|
|
8605fea678 | ||
|
|
d9b6585804 | ||
|
|
30b8d7bdad | ||
|
|
436624b7a3 | ||
|
|
268a39fc61 | ||
|
|
317f432a20 | ||
|
|
cbcdcf4ad5 | ||
|
|
ce13fc5f03 | ||
|
|
c16f813794 | ||
|
|
6e9e15b929 | ||
|
|
776bda60ca | ||
|
|
322aa6887a | ||
|
|
f256934f93 | ||
|
|
2658c5c539 | ||
|
|
4e0890a50d | ||
|
|
73e1eeb57e | ||
|
|
514014f4fc | ||
|
|
508cf6b61d | ||
|
|
8782bf8cb0 | ||
|
|
94bdb6b97a | ||
|
|
2c90b128c3 | ||
|
|
cf465bdd81 | ||
|
|
05442bc667 | ||
|
|
aad1d848a6 | ||
|
|
09a1ce9b2b | ||
|
|
d9c808ded8 | ||
|
|
ba6757ad11 | ||
|
|
e18dda3e08 | ||
|
|
1fa69ebcc9 | ||
|
|
f0db8329be | ||
|
|
6ce372cfb6 | ||
|
|
7fc30b715a | ||
|
|
07c07ad806 | ||
|
|
d7e7001afd | ||
|
|
df6831694b | ||
|
|
14aa9d7108 | ||
|
|
3794c178be | ||
|
|
560cd6ca6c | ||
|
|
6996141448 | ||
|
|
968f2ad7c9 | ||
|
|
c05f23d99d | ||
|
|
19224505e8 | ||
|
|
06b0484ea3 | ||
|
|
9e4c8ad7d7 | ||
|
|
e103adaa52 | ||
|
|
89e3587c15 | ||
|
|
446285f348 | ||
|
|
c03d3a58ec | ||
|
|
334c15e879 | ||
|
|
c3d9a51d15 | ||
|
|
fddbf0728a | ||
|
|
32aa9933be | ||
|
|
a354f7bba1 | ||
|
|
b7811af211 | ||
|
|
471ec26873 | ||
|
|
a4a6e8dabe | ||
|
|
4416b2a467 | ||
|
|
c69ff172e5 | ||
|
|
9c38e904dd | ||
|
|
fd4f6941bf | ||
|
|
9f28643ac7 | ||
|
|
9135d29346 | ||
|
|
96af524aad | ||
|
|
3a9a1c93dd | ||
|
|
b0618b9c9a | ||
|
|
c789490294 | ||
|
|
bf2fbfc52d | ||
|
|
3613edf321 | ||
|
|
b06c0f8ff9 | ||
|
|
05900fbabf | ||
|
|
51ae26128b | ||
|
|
1b66332f06 | ||
|
|
7b4b325e9d | ||
|
|
5c09cc37c9 | ||
|
|
e96e0e113a | ||
|
|
2d7ec43173 | ||
|
|
536da88df7 | ||
|
|
09b5838fda | ||
|
|
415fac82b2 | ||
|
|
eb30fb7330 | ||
|
|
be54e89ba6 | ||
|
|
5f17d281a6 | ||
|
|
d918f7e51b | ||
|
|
cb6cf107ba | ||
|
|
c9546c2419 | ||
|
|
2a1b2ed24d | ||
|
|
889df336d6 | ||
|
|
12d288ffcd | ||
|
|
4dd453f92c | ||
|
|
e42be00054 | ||
|
|
c3491ebdc3 | ||
|
|
f39f103dbe | ||
|
|
cf06c25904 | ||
|
|
dde92d1269 | ||
|
|
46455e854e | ||
|
|
473d391ea0 | ||
|
|
1bc71b8f9e | ||
|
|
4773ffba58 | ||
|
|
b5c5e807ea | ||
|
|
471dbace44 | ||
|
|
507e1039a4 | ||
|
|
a8d5070e98 | ||
|
|
9e5e76080d | ||
|
|
e40488b56e | ||
|
|
e5629321db | ||
|
|
4cb755d375 | ||
|
|
9eb59946ce | ||
|
|
a623616629 | ||
|
|
798b3bc158 | ||
|
|
18044abbb6 | ||
|
|
6e515b00ca | ||
|
|
96a949189d | ||
|
|
9bd4385002 | ||
|
|
f0a85acb63 | ||
|
|
7fc0525577 | ||
|
|
18150fa70f | ||
|
|
ffea61b540 | ||
|
|
d6350c672a | ||
|
|
6176e16275 | ||
|
|
47e621aaf3 | ||
|
|
5e84692e7e | ||
|
|
6ab541ea4e | ||
|
|
034f427638 | ||
|
|
143036aa0a | ||
|
|
ec001a067f | ||
|
|
41239caa97 | ||
|
|
30718a3be5 | ||
|
|
ca44da8bdf | ||
|
|
4d6ca07596 | ||
|
|
ba6a815711 | ||
|
|
9ac77140b6 | ||
|
|
0bcfdd2c07 | ||
|
|
b1f4606231 | ||
|
|
8ec024a8fd | ||
|
|
6c6290d20f | ||
|
|
707fbcf649 | ||
|
|
fea3cc2564 | ||
|
|
1251750403 | ||
|
|
40045ae02d | ||
|
|
ae28139206 | ||
|
|
c6b0e0cee3 | ||
|
|
31644f1542 | ||
|
|
aeba695431 | ||
|
|
e7165e8b6e | ||
|
|
392821700c | ||
|
|
133e98f7e1 | ||
|
|
52a0982948 | ||
|
|
ad0116ac0b | ||
|
|
05487c9f66 | ||
|
|
e121f8e81b | ||
|
|
94dc7bb279 | ||
|
|
93613ec5e1 | ||
|
|
5089cb045e | ||
|
|
fa26b6815d | ||
|
|
cde5527c9f | ||
|
|
0674aa7d8f | ||
|
|
c56da459bc | ||
|
|
77d9c8dcf0 | ||
|
|
cae29ab96e | ||
|
|
8bc32ebe5e | ||
|
|
f461d4099c | ||
|
|
32d16803a6 | ||
|
|
302149e02d | ||
|
|
f2b4ed2893 | ||
|
|
73b1d4d877 | ||
|
|
14afeb0e53 | ||
|
|
68ddbf82e0 | ||
|
|
c020c6bbf7 | ||
|
|
b7ab716ee5 | ||
|
|
b47eccbff0 | ||
|
|
f798d2df92 | ||
|
|
bf5aa56dc0 | ||
|
|
0609686803 | ||
|
|
2a41320963 | ||
|
|
f5d4c9fc1c | ||
|
|
23d6233d42 | ||
|
|
e169d97713 | ||
|
|
1862d1d827 | ||
|
|
07bad48dbc | ||
|
|
a05842d051 | ||
|
|
d8879486bd | ||
|
|
e4292702aa | ||
|
|
59f1e59ff1 | ||
|
|
fa3010a50e | ||
|
|
11d700d0a7 | ||
|
|
5c351c8d17 | ||
|
|
f14ac7146c | ||
|
|
69280ee64f | ||
|
|
f66ab39a40 | ||
|
|
0cea9346e1 | ||
|
|
1a7df66e10 | ||
|
|
7e6c33152e | ||
|
|
e48ca2892b | ||
|
|
5d6ce6c986 | ||
|
|
7b08953c18 | ||
|
|
3d1a74dda9 | ||
|
|
877ba28d85 | ||
|
|
f68a80879e | ||
|
|
7b0ab173dd | ||
|
|
4a5534f330 | ||
|
|
010640ccc8 | ||
|
|
f6c09da968 | ||
|
|
2c21ecd1df | ||
|
|
e9bc79d5ff | ||
|
|
44599e9e7e | ||
|
|
910fe62d92 | ||
|
|
5a1beaffb8 | ||
|
|
bdf5434d7b | ||
|
|
096a3dc645 | ||
|
|
11aefa3e42 | ||
|
|
f7eb51204a | ||
|
|
3fde8f3b27 | ||
|
|
1795740517 | ||
|
|
4783df5737 | ||
|
|
b00e63e37e | ||
|
|
322165f404 | ||
|
|
074e4b3144 | ||
|
|
bd51adebdf | ||
|
|
afffb13902 | ||
|
|
afe5e96aee | ||
|
|
95f3c0d69d | ||
|
|
185111d197 | ||
|
|
439b44ee93 | ||
|
|
d39ddac486 | ||
|
|
f3a8e98511 | ||
|
|
703ab1eee0 | ||
|
|
d3028e0227 | ||
|
|
fe87ca8117 | ||
|
|
fa39705dcb | ||
|
|
e53c9d1540 | ||
|
|
8d3ca534c4 | ||
|
|
6f8a652653 | ||
|
|
cfed9f3958 | ||
|
|
44da572893 | ||
|
|
57b0ca644e | ||
|
|
f750c0ca06 | ||
|
|
25ca331dc8 | ||
|
|
a209c1a7dc | ||
|
|
0873e451a8 | ||
|
|
0eea760443 | ||
|
|
62ae7339fe | ||
|
|
887ffd1116 | ||
|
|
d5975a4023 | ||
|
|
057d6aa497 | ||
|
|
395e7b390a | ||
|
|
c36e3612e7 | ||
|
|
8b6ef48046 | ||
|
|
31c33c87c5 | ||
|
|
8c9a33b737 | ||
|
|
286810d25b | ||
|
|
8c5a279ba2 | ||
|
|
c7081f90e8 | ||
|
|
6e921912d7 | ||
|
|
c132c66ac6 | ||
|
|
e3bf553dc3 | ||
|
|
7c3115ded6 | ||
|
|
db2fa98b52 | ||
|
|
42ee90b4d5 | ||
|
|
f5d2b869c0 | ||
|
|
919a2fbfda | ||
|
|
09c2dc2e85 | ||
|
|
a3c1082bb1 | ||
|
|
3a13a7bfda | ||
|
|
1a698fae33 | ||
|
|
0772f6fe16 | ||
|
|
f63e6f3e1b | ||
|
|
b636979127 | ||
|
|
55bb584360 | ||
|
|
5cce71db44 | ||
|
|
adb8d08995 | ||
|
|
6baddab4da | ||
|
|
5e51f2a6ed | ||
|
|
701efe8903 | ||
|
|
791111b060 | ||
|
|
999925744c | ||
|
|
7188e0486a | ||
|
|
993c892ad9 | ||
|
|
92b1a5bf3e | ||
|
|
4993925471 | ||
|
|
11e69eab36 | ||
|
|
e22ff3af36 | ||
|
|
7a5053dfb2 | ||
|
|
4d04e5522c | ||
|
|
ff1d4a1d4e | ||
|
|
7a6848595f | ||
|
|
d39b44c4d3 | ||
|
|
6a4ce0a620 | ||
|
|
494c7bda58 | ||
|
|
8000235591 | ||
|
|
64e1df5cdb | ||
|
|
3817246915 | ||
|
|
c5ce44eaaa | ||
|
|
75e11daa95 | ||
|
|
91b06fbe03 | ||
|
|
fb7393e4bb | ||
|
|
0a8c642e93 | ||
|
|
6b848f5038 | ||
|
|
eee6e78ec1 | ||
|
|
9c3ced06d5 | ||
|
|
6a45c3e25a | ||
|
|
d6acd2ee7d | ||
|
|
ee78527a9e | ||
|
|
b08f415f80 | ||
|
|
80afab57d3 | ||
|
|
12297982c8 | ||
|
|
7da7929edf | ||
|
|
aad90154e5 | ||
|
|
7b99df6c48 | ||
|
|
70fb78d031 | ||
|
|
d41e46be14 | ||
|
|
73364fa2db | ||
|
|
44f6446f6c | ||
|
|
dfb37d9a44 | ||
|
|
ab1de2942c | ||
|
|
2ff3ae86d0 | ||
|
|
906dfd3b6c | ||
|
|
a824e7b379 | ||
|
|
a2b358613b | ||
|
|
99b7a5b482 | ||
|
|
725dac204f | ||
|
|
ccc8f7ed37 | ||
|
|
8d38bf8ded | ||
|
|
cdfc67661e | ||
|
|
4fba939726 | ||
|
|
cf2b48fc0c | ||
|
|
962b3659c6 | ||
|
|
717242636a | ||
|
|
29055ab4a6 | ||
|
|
29073cf711 | ||
|
|
cf82d6f0a5 | ||
|
|
8fd984a767 | ||
|
|
1db415d897 | ||
|
|
914e14bb8d | ||
|
|
c8eff6ec07 | ||
|
|
107a64003d | ||
|
|
47d2e7b658 | ||
|
|
13b4fd9b9f | ||
|
|
e95eafe02a | ||
|
|
2cc83ed1fe | ||
|
|
b052d646ee | ||
|
|
174ef52df7 | ||
|
|
1fda3ba969 | ||
|
|
f314328294 | ||
|
|
0f24d66234 | ||
|
|
fd028253fe | ||
|
|
2e4ff723aa | ||
|
|
1218ddd5dc | ||
|
|
330eaf3ed6 | ||
|
|
2758a0ec53 | ||
|
|
1f63fc4fea | ||
|
|
de481bcd67 | ||
|
|
12c96d06e4 | ||
|
|
b572b91783 | ||
|
|
ad29fbdd55 | ||
|
|
f21ad9b879 | ||
|
|
8851a246de | ||
|
|
23cfdfffba | ||
|
|
f5f0d1a43b | ||
|
|
8bfbfe3e46 | ||
|
|
21615cb158 | ||
|
|
b6b6766000 | ||
|
|
f37ec60456 | ||
|
|
b35861005e | ||
|
|
123559478e | ||
|
|
bbd8fb2342 | ||
|
|
310a83427c | ||
|
|
0b6546aa8d | ||
|
|
21afc1933a | ||
|
|
e800af70bb | ||
|
|
40d36165a4 | ||
|
|
f10b76f087 | ||
|
|
2efc566b14 | ||
|
|
089c8b7b0a | ||
|
|
134d9f1e98 | ||
|
|
7e0863ef81 | ||
|
|
e96dd96864 | ||
|
|
8ff947e83a | ||
|
|
d5fd7ff339 | ||
|
|
8a223b8178 | ||
|
|
19193cd2a4 | ||
|
|
c15f670f2c | ||
|
|
f583f9a2d4 | ||
|
|
70734a1f28 | ||
|
|
b458797979 | ||
|
|
754b1df51d | ||
|
|
0da079c98c | ||
|
|
7be91fc92c | ||
|
|
415cdb1ef9 | ||
|
|
17c5361d43 | ||
|
|
6cf99b4415 | ||
|
|
7a5d4ecdc9 | ||
|
|
1a7792e06f | ||
|
|
95e863cb6d | ||
|
|
9b55505cdf | ||
|
|
b687960c5d | ||
|
|
10ccbc6a31 | ||
|
|
0e2e7e533a | ||
|
|
d71910cdc2 | ||
|
|
3cb7c61026 | ||
|
|
08c73444c4 | ||
|
|
e686b64ff3 | ||
|
|
7d1fae001b | ||
|
|
f190cdb3ec | ||
|
|
6275435567 | ||
|
|
5a90b14099 | ||
|
|
bca7c16435 | ||
|
|
925962505e | ||
|
|
e95529aca0 | ||
|
|
ed7bcf787c | ||
|
|
87728c4452 | ||
|
|
134f62a776 | ||
|
|
8fb53964e4 | ||
|
|
eb7daf1b74 | ||
|
|
ea665cb743 | ||
|
|
2287720201 | ||
|
|
91b57a3f28 | ||
|
|
97d69bd437 | ||
|
|
3c4ac0e5a2 | ||
|
|
9822749c92 | ||
|
|
d9fc95c2a1 | ||
|
|
788ccffb2b | ||
|
|
1fea829760 | ||
|
|
53b89b704c | ||
|
|
a949c35228 | ||
|
|
81fbcbc1b2 | ||
|
|
05317f1664 | ||
|
|
8a42a68a4f | ||
|
|
984db6798c | ||
|
|
ba9c354e1b | ||
|
|
f1dc0a6fcb | ||
|
|
13d715e2a5 | ||
|
|
1fceeab0fc | ||
|
|
51020cd635 | ||
|
|
4d05077ec3 | ||
|
|
5b6a4c4563 | ||
|
|
8e834cbdfc | ||
|
|
27233364bc | ||
|
|
685532d44a | ||
|
|
692c8036b7 | ||
|
|
f9715c442a | ||
|
|
e73a1c3298 | ||
|
|
129a6f187e | ||
|
|
7710ed00d3 | ||
|
|
fcc17254f6 | ||
|
|
6edd3705a6 | ||
|
|
58f988bf33 | ||
|
|
34a0615ac1 | ||
|
|
5a74f08c84 | ||
|
|
bdce56df30 | ||
|
|
f4a7b652c7 | ||
|
|
cb479bf199 | ||
|
|
b10421a9b4 | ||
|
|
ef77e45427 | ||
|
|
823b5288f9 | ||
|
|
c17951e3f6 | ||
|
|
f0a29de87d | ||
|
|
b60271a6cf | ||
|
|
f00e6b69f2 | ||
|
|
cc8515041b | ||
|
|
01512e27a0 | ||
|
|
301b79c688 | ||
|
|
98654634ca | ||
|
|
1812bd17a5 | ||
|
|
7b6486cd30 | ||
|
|
223fe14d85 | ||
|
|
5eb775462d | ||
|
|
ed60687f11 | ||
|
|
66d9514e12 | ||
|
|
37cc852bfc | ||
|
|
10eb20e44b | ||
|
|
7744e95aea | ||
|
|
3f871f0805 | ||
|
|
db7b945262 | ||
|
|
64aef5c13c | ||
|
|
3fe5a368ac | ||
|
|
f23a8480e7 | ||
|
|
5d6a48b568 | ||
|
|
df48df5ede | ||
|
|
51fcfe980f | ||
|
|
e983506e23 | ||
|
|
456bc80697 | ||
|
|
5be22a073b | ||
|
|
26a3ec9ae3 | ||
|
|
8d6d47a5c6 | ||
|
|
5adae77e39 | ||
|
|
79840126b9 | ||
|
|
05d0248b18 | ||
|
|
2a61e1cace | ||
|
|
abf850af91 | ||
|
|
0722db10df | ||
|
|
1fece09687 | ||
|
|
9f0db9a02a | ||
|
|
6f499013f6 | ||
|
|
29de419c87 | ||
|
|
9bde9c9fbb | ||
|
|
865c4984f6 | ||
|
|
987ad12181 | ||
|
|
003f3b4fe1 | ||
|
|
18f6a659f4 | ||
|
|
6e0de0d196 | ||
|
|
4d464cc9fb | ||
|
|
4030807b9b | ||
|
|
174fbe56b0 | ||
|
|
ab7971de42 | ||
|
|
65224213d0 | ||
|
|
992f5aaed7 | ||
|
|
298b169dc0 | ||
|
|
3fd46e8cbe | ||
|
|
9c10d4aa3f | ||
|
|
a7b8768e56 | ||
|
|
5d482abb67 | ||
|
|
1871a1632e | ||
|
|
4e19be7e43 | ||
|
|
ff973caa67 | ||
|
|
362be9f344 | ||
|
|
904e3a3492 | ||
|
|
404c398b59 | ||
|
|
1f92212497 | ||
|
|
d7f65cbbcd | ||
|
|
b4124d0d92 | ||
|
|
f3e9413b2d | ||
|
|
d09527b75e | ||
|
|
ad05321d94 | ||
|
|
822f6a9fa6 | ||
|
|
b846311173 | ||
|
|
cdf5634cf2 | ||
|
|
2d02933d35 | ||
|
|
11991772f8 | ||
|
|
ab9573f108 | ||
|
|
195673b01a | ||
|
|
c656cdd951 | ||
|
|
1bec1d5f26 | ||
|
|
7a12d1e322 | ||
|
|
25ddf1d1e9 | ||
|
|
6e4c2fe786 | ||
|
|
a7c9856851 | ||
|
|
78382209d3 | ||
|
|
c32bfa6eeb | ||
|
|
815083d1e8 | ||
|
|
b432bb0cf3 | ||
|
|
22bc2d24ab | ||
|
|
e5cf139b03 | ||
|
|
ec7b5861f9 | ||
|
|
58ac82b0a7 | ||
|
|
6d1f2986fe | ||
|
|
166959db41 | ||
|
|
367a948ff0 | ||
|
|
6d996834f3 | ||
|
|
e731e658ba | ||
|
|
f62e8f594d | ||
|
|
880ab910f7 | ||
|
|
2da4b70e3a | ||
|
|
f58014e2b3 | ||
|
|
dbfa61a156 | ||
|
|
8294d4ae8e | ||
|
|
8daffa76dd | ||
|
|
d55f554d7b | ||
|
|
a0472e11a9 | ||
|
|
c8d34fbe8c | ||
|
|
6ccb9814f8 | ||
|
|
733813700e | ||
|
|
af1d46f184 | ||
|
|
5f7fffbe81 | ||
|
|
71e11950d9 | ||
|
|
a1b74b0758 | ||
|
|
f2515405b5 | ||
|
|
3dc15623d5 | ||
|
|
ffe1b6e03a | ||
|
|
7265921719 | ||
|
|
58e24cea4b | ||
|
|
aa8336ee94 | ||
|
|
733ff9a05b | ||
|
|
1204996378 | ||
|
|
c0f5266bc4 | ||
|
|
1ca57407d3 | ||
|
|
e082d8cc75 | ||
|
|
94237df7b5 | ||
|
|
9377bef603 | ||
|
|
6ee827bd8a | ||
|
|
351d464448 | ||
|
|
fcacdb2791 | ||
|
|
e25b89d49a | ||
|
|
6385b82c2b | ||
|
|
622b62cff1 | ||
|
|
e206505061 | ||
|
|
4955f05fa3 | ||
|
|
258d1c0521 | ||
|
|
734b872e41 | ||
|
|
8405693325 | ||
|
|
910574361c | ||
|
|
0a8fa3a8ba | ||
|
|
0094064f4a | ||
|
|
7e37f8e3ed | ||
|
|
945a53353f | ||
|
|
e3111729c6 | ||
|
|
d41c786554 | ||
|
|
c40e26ada9 | ||
|
|
9a8364566c | ||
|
|
75aefb89fd | ||
|
|
a3e60af35e | ||
|
|
bc87fadc03 | ||
|
|
76e295f606 | ||
|
|
ad7544ce90 | ||
|
|
01a5355713 | ||
|
|
abcb93d0bd | ||
|
|
e45b5b35e3 | ||
|
|
a6e44a349a | ||
|
|
fe22f1e46a | ||
|
|
89bb079e50 | ||
|
|
f2e32857cb | ||
|
|
7492b46c43 | ||
|
|
ad20a404a9 | ||
|
|
496006d629 | ||
|
|
f2f8ecac73 | ||
|
|
204643b8da | ||
|
|
4f206c5f1e | ||
|
|
1e09c63282 | ||
|
|
c88248acb1 | ||
|
|
547d752df5 | ||
|
|
8b40c2e372 | ||
|
|
f51764f8d0 | ||
|
|
64f95fec69 | ||
|
|
8ca35fdf4f | ||
|
|
d87921c12a | ||
|
|
ae3de43a62 | ||
|
|
fbb5e78176 | ||
|
|
10de265b4f | ||
|
|
4a357ee6e9 | ||
|
|
40b96bfffd | ||
|
|
f53c68695d | ||
|
|
cfc2e3e228 | ||
|
|
25398fe622 | ||
|
|
a7c3740dee | ||
|
|
7de056437f | ||
|
|
cfa9de4047 | ||
|
|
c48fe567f7 | ||
|
|
c4b6789666 | ||
|
|
6404c4bca8 | ||
|
|
9423712696 | ||
|
|
5018b1a426 | ||
|
|
1336ca5d9d | ||
|
|
5e3046d86a | ||
|
|
e45536ae93 | ||
|
|
48254acb4e | ||
|
|
20b408ff3e | ||
|
|
4a579b5752 | ||
|
|
edf5995a1e | ||
|
|
6b2efdc9c3 | ||
|
|
17f5aed6dd | ||
|
|
fb44ded002 | ||
|
|
fea711cebb | ||
|
|
57efe6f75d | ||
|
|
6d71f75817 | ||
|
|
d570b1731e | ||
|
|
8fc263f62f | ||
|
|
3886f889ab | ||
|
|
f96942bdc0 | ||
|
|
f628a3fd7e | ||
|
|
f078f79c90 | ||
|
|
af45d22fb7 | ||
|
|
c3a7d8c433 | ||
|
|
6c7d81c325 | ||
|
|
a6f8c53924 | ||
|
|
6d5bbd913c | ||
|
|
4e957d3484 | ||
|
|
454ec3e74c | ||
|
|
ddf497623a | ||
|
|
746cb0493f | ||
|
|
2e27587f15 | ||
|
|
84c27f3694 | ||
|
|
42c0752370 | ||
|
|
3615b1620e | ||
|
|
e42553867f | ||
|
|
955347e426 | ||
|
|
468f1aa312 | ||
|
|
afb64c873a | ||
|
|
8f17468f51 | ||
|
|
78b58122c1 | ||
|
|
97ac351c5e | ||
|
|
c7f442425d | ||
|
|
df0963446f | ||
|
|
ef873b4b60 | ||
|
|
6a267debf5 | ||
|
|
334bb30f9e | ||
|
|
da9b2ee6b9 | ||
|
|
3ebf23e39b | ||
|
|
b5f54f1624 | ||
|
|
33da5bf81f | ||
|
|
43e3fd9c2c | ||
|
|
0201a5aadb | ||
|
|
fa38e9fa47 | ||
|
|
382df8e714 | ||
|
|
c5db729191 | ||
|
|
e0a294849a | ||
|
|
fcfe8e1ac8 | ||
|
|
4f1085beb3 | ||
|
|
e325a0003b | ||
|
|
e967e5ed11 | ||
|
|
d4aa062441 | ||
|
|
e5735cde67 | ||
|
|
75f3f3c8c4 | ||
|
|
3b8d670c81 | ||
|
|
8b7158f169 | ||
|
|
82c1a3a9ea | ||
|
|
53d0f41ba5 | ||
|
|
a5091c56cb | ||
|
|
daca1f38cf | ||
|
|
5df8d29fe4 | ||
|
|
8c38a8381c | ||
|
|
d22446d1dd | ||
|
|
c037e17220 | ||
|
|
506e17ace2 | ||
|
|
e88f09842d | ||
|
|
e79b1398b0 | ||
|
|
371b5e2a6e | ||
|
|
a4629db520 | ||
|
|
aaa364da54 | ||
|
|
a0c34dab54 | ||
|
|
1d815464f6 | ||
|
|
461f7491e8 | ||
|
|
abe8cfdf1c | ||
|
|
d9e3f9ca68 | ||
|
|
e08252cad7 | ||
|
|
9206ad52b8 | ||
|
|
57fd8c97b4 | ||
|
|
f9f665be62 | ||
|
|
3f1fb9d29d | ||
|
|
8afe853b76 | ||
|
|
5ec62358b3 | ||
|
|
dcda3e43b9 | ||
|
|
81fc6f16cf | ||
|
|
07f4218bf4 | ||
|
|
529d223ee1 | ||
|
|
3c4f04f3aa | ||
|
|
1fd557d549 | ||
|
|
9cac89347e | ||
|
|
020372dde0 | ||
|
|
bf49952275 | ||
|
|
491bd0e74f | ||
|
|
5c43cab499 | ||
|
|
317802a26e | ||
|
|
4bd090887e | ||
|
|
2fc0edf85b | ||
|
|
4192e5de2d | ||
|
|
443835096b | ||
|
|
d3855a9158 | ||
|
|
1d1ff8db7a | ||
|
|
937b0d3419 | ||
|
|
96bfe406ee | ||
|
|
271bfcbac7 | ||
|
|
5e40168829 | ||
|
|
bc9e4abee5 | ||
|
|
8d28a077fc | ||
|
|
61b48f1089 | ||
|
|
d4207e2936 | ||
|
|
65a62b8d32 | ||
|
|
cf54a780cf | ||
|
|
6d3cbf192e | ||
|
|
d94d0ac720 | ||
|
|
3942a08e0c | ||
|
|
3092c17080 | ||
|
|
b8a88719a7 | ||
|
|
e22d91b8c4 | ||
|
|
5adc09150d | ||
|
|
f3a0127715 | ||
|
|
b8d95537e0 | ||
|
|
398c44e334 | ||
|
|
74284c3271 | ||
|
|
1e9759679f | ||
|
|
e4096fb6f2 | ||
|
|
a3c372f602 | ||
|
|
42a22c280d | ||
|
|
adc23253e4 | ||
|
|
5025113da0 | ||
|
|
5fb03b6891 | ||
|
|
0fcac14fd7 | ||
|
|
7edf3c86c3 | ||
|
|
ecac89521b | ||
|
|
51ba929fc6 | ||
|
|
b6e4471458 | ||
|
|
763eaed2af | ||
|
|
e1ab8a9b42 | ||
|
|
daa9f116d0 | ||
|
|
c0ab8e8256 | ||
|
|
6d927ef83d | ||
|
|
78e815c861 | ||
|
|
8284a7cb89 | ||
|
|
03f0e717a7 | ||
|
|
778b8fdb2b | ||
|
|
d0bf163641 | ||
|
|
685a7e8ac5 | ||
|
|
a866d881e5 | ||
|
|
0527c4eae6 | ||
|
|
bdd3beb257 | ||
|
|
3b4b134328 | ||
|
|
4f38cb68e4 | ||
|
|
3d52769a47 | ||
|
|
8cb426120b | ||
|
|
9e8e5a1e81 | ||
|
|
88e3c83138 | ||
|
|
62aa942bf5 | ||
|
|
fc0b08042d | ||
|
|
225efcaf02 | ||
|
|
115357ddd1 | ||
|
|
a3507e7f58 | ||
|
|
189a490e27 | ||
|
|
52afc1ace5 | ||
|
|
f0c427b2d2 | ||
|
|
ff90fe52ee | ||
|
|
02ee5bcb6a | ||
|
|
16c1e214d6 | ||
|
|
019be59079 | ||
|
|
84caa50a08 | ||
|
|
59b191c789 | ||
|
|
6e7be1ec8d | ||
|
|
f4363c8a86 | ||
|
|
211bdb0bb7 | ||
|
|
c3709e8b04 | ||
|
|
c79b1fa87a | ||
|
|
964cc1b206 | ||
|
|
b31b08a30e | ||
|
|
15228f4463 | ||
|
|
e4102aae8d | ||
|
|
ac4fc783ae | ||
|
|
09ff71105a | ||
|
|
290736ae06 | ||
|
|
ea9db0ebbe | ||
|
|
82892f3e0e | ||
|
|
1640b6a7f3 | ||
|
|
fdd3beaf58 | ||
|
|
ae770996f6 | ||
|
|
cdb1323515 | ||
|
|
49b682edca | ||
|
|
e1f41e83d1 | ||
|
|
acbc517b55 | ||
|
|
253b62f8b0 | ||
|
|
cb97a80350 | ||
|
|
84897d4834 | ||
|
|
a9696d8501 | ||
|
|
8828fd17a0 | ||
|
|
aa3d24ea2a | ||
|
|
68d413e923 | ||
|
|
ee5a1c9002 | ||
|
|
2861c874ed | ||
|
|
91a897dd74 | ||
|
|
e64faa781c | ||
|
|
9bbe745a33 | ||
|
|
658d5a8b7e | ||
|
|
4e6b27144a | ||
|
|
26a78dbaa4 | ||
|
|
7e7b973481 | ||
|
|
646c8ac657 | ||
|
|
236631141f | ||
|
|
c4f10edc95 | ||
|
|
e42df1d859 | ||
|
|
18258000cd | ||
|
|
c494feb7f7 | ||
|
|
de8973d77a | ||
|
|
942063d4ef | ||
|
|
c15387e972 | ||
|
|
c31462d51b | ||
|
|
8b1fcea7ec | ||
|
|
a48f5b07c5 | ||
|
|
ca20b5951d | ||
|
|
f3b30443aa | ||
|
|
ccae898885 | ||
|
|
982d8f53f2 | ||
|
|
34bde45a2c | ||
|
|
9058f28788 | ||
|
|
bf57f289bf | ||
|
|
4007f82765 | ||
|
|
919e1d7933 | ||
|
|
74ebdaf4e8 | ||
|
|
c5871f4c2a | ||
|
|
58981a41e9 | ||
|
|
86435b8a4b | ||
|
|
980863366c | ||
|
|
7324feef89 | ||
|
|
5bd6b672d0 | ||
|
|
869d80a34b | ||
|
|
a3c3573d67 | ||
|
|
b4f0b05455 | ||
|
|
df8453d387 | ||
|
|
73abbace85 | ||
|
|
d2b48fdea2 | ||
|
|
caef94d851 | ||
|
|
9728dd8699 | ||
|
|
b497b2234d | ||
|
|
211a66cf33 | ||
|
|
ca1346ee03 | ||
|
|
2a43d66e11 | ||
|
|
65c477414b | ||
|
|
39a648bce1 | ||
|
|
11532c5be9 | ||
|
|
cc4441b50f | ||
|
|
174c9326a4 | ||
|
|
2473c3e49f | ||
|
|
2592067f95 | ||
|
|
9c95994dab | ||
|
|
db5bd646b9 | ||
|
|
4416158ece | ||
|
|
7f0dad7901 | ||
|
|
6a792324b0 | ||
|
|
f2311b56fc | ||
|
|
84113cba2c | ||
|
|
97000fc4e0 | ||
|
|
13a8b6cc4e | ||
|
|
76c95abbb5 | ||
|
|
33ef823645 | ||
|
|
817eb66167 | ||
|
|
f05f6cb44d | ||
|
|
36433a9f4d | ||
|
|
db84f32981 | ||
|
|
af1a53cb05 | ||
|
|
8fba935bba | ||
|
|
8c442f599b | ||
|
|
04bb80f157 | ||
|
|
f7287553e9 | ||
|
|
ae44580371 | ||
|
|
fa2adaf2ff | ||
|
|
af7901dcb2 | ||
|
|
5a4459856c | ||
|
|
93c2b8b555 | ||
|
|
0b2c8d1fa2 | ||
|
|
312e5b8756 | ||
|
|
aa0f20b93e | ||
|
|
78485fd8df | ||
|
|
4e03fb361f | ||
|
|
82922bf0d7 | ||
|
|
e1362ead3c | ||
|
|
c2ee9ca3e0 |
314
.cspell.json
314
.cspell.json
@@ -26,109 +26,263 @@
|
|||||||
"words": [
|
"words": [
|
||||||
// Rust
|
// Rust
|
||||||
"ahash",
|
"ahash",
|
||||||
"bitflags",
|
"bidi",
|
||||||
|
"biguint",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cstring",
|
"bitflags",
|
||||||
|
"bstr",
|
||||||
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
"peekable",
|
"consts",
|
||||||
|
"cstring",
|
||||||
|
"flate2",
|
||||||
|
"fract",
|
||||||
|
"hasher",
|
||||||
|
"idents",
|
||||||
|
"indexmap",
|
||||||
|
"insta",
|
||||||
|
"keccak",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"memmap",
|
"libc",
|
||||||
|
"libz",
|
||||||
|
"longlong",
|
||||||
"Manually",
|
"Manually",
|
||||||
|
"maplit",
|
||||||
|
"memmap",
|
||||||
|
"metas",
|
||||||
|
"modpow",
|
||||||
|
"nanos",
|
||||||
|
"objclass",
|
||||||
|
"peekable",
|
||||||
|
"powc",
|
||||||
|
"powf",
|
||||||
|
"prepended",
|
||||||
|
"punct",
|
||||||
|
"replacen",
|
||||||
|
"rsplitn",
|
||||||
"rustc",
|
"rustc",
|
||||||
"unistd",
|
"rustfmt",
|
||||||
|
"seekfrom",
|
||||||
|
"splitn",
|
||||||
|
"subsec",
|
||||||
|
"timsort",
|
||||||
|
"trai",
|
||||||
|
"ulonglong",
|
||||||
"unic",
|
"unic",
|
||||||
|
"unistd",
|
||||||
|
"winapi",
|
||||||
|
"winsock",
|
||||||
// Python
|
// Python
|
||||||
"cformat",
|
|
||||||
"cpython",
|
|
||||||
"fspath",
|
|
||||||
"kwarg",
|
|
||||||
"kwargs",
|
|
||||||
"vararg",
|
|
||||||
"varargs",
|
|
||||||
"metaclass",
|
|
||||||
"metaclasses",
|
|
||||||
"fstring",
|
|
||||||
"fstrings",
|
|
||||||
"docstring",
|
|
||||||
"docstrings",
|
|
||||||
"fileencoding",
|
|
||||||
"linearization",
|
|
||||||
"linearize",
|
|
||||||
"PYTHONDEBUG",
|
|
||||||
"PYTHONINSPECT",
|
|
||||||
"PYTHONPATH",
|
|
||||||
"PYTHONHOME",
|
|
||||||
"PYTHONPATH",
|
|
||||||
"PYTHONVERBOSE",
|
|
||||||
"PYTHONOPTIMIZE",
|
|
||||||
"PYTHONWARNINGS",
|
|
||||||
"basicsize",
|
|
||||||
"itemsize",
|
|
||||||
"getattro",
|
|
||||||
"setattro",
|
|
||||||
"iternext",
|
|
||||||
"maxsplit",
|
|
||||||
"fdel",
|
|
||||||
"subclasscheck",
|
|
||||||
"qualname",
|
|
||||||
"eventmask",
|
|
||||||
"instanceof",
|
|
||||||
"abstractmethods",
|
"abstractmethods",
|
||||||
"aiter",
|
"aiter",
|
||||||
"anext",
|
"anext",
|
||||||
"rdiv",
|
"arrayiterator",
|
||||||
"idiv",
|
"arraytype",
|
||||||
"ndim",
|
"asend",
|
||||||
"varnames",
|
"athrow",
|
||||||
"getweakrefs",
|
"basicsize",
|
||||||
"getweakrefcount",
|
"cformat",
|
||||||
"stacklevel",
|
"classcell",
|
||||||
"MemoryView",
|
"closesocket",
|
||||||
"warningregistry",
|
"codepoint",
|
||||||
|
"codepoints",
|
||||||
|
"cpython",
|
||||||
|
"decompressor",
|
||||||
"defaultaction",
|
"defaultaction",
|
||||||
"unraisablehook",
|
|
||||||
"descr",
|
"descr",
|
||||||
"xopts",
|
"dictcomp",
|
||||||
|
"dictitems",
|
||||||
|
"dictkeys",
|
||||||
|
"dictview",
|
||||||
|
"docstring",
|
||||||
|
"docstrings",
|
||||||
|
"dunder",
|
||||||
|
"eventmask",
|
||||||
|
"fdel",
|
||||||
|
"fget",
|
||||||
|
"fileencoding",
|
||||||
|
"fillchar",
|
||||||
|
"finallyhandler",
|
||||||
|
"frombytes",
|
||||||
|
"fromhex",
|
||||||
|
"fromunicode",
|
||||||
|
"fset",
|
||||||
|
"fspath",
|
||||||
|
"fstring",
|
||||||
|
"fstrings",
|
||||||
|
"genexpr",
|
||||||
|
"getattro",
|
||||||
|
"getformat",
|
||||||
|
"getnewargs",
|
||||||
|
"getweakrefcount",
|
||||||
|
"getweakrefs",
|
||||||
|
"hostnames",
|
||||||
|
"idiv",
|
||||||
|
"impls",
|
||||||
|
"infj",
|
||||||
|
"instancecheck",
|
||||||
|
"instanceof",
|
||||||
|
"isabstractmethod",
|
||||||
|
"itemiterator",
|
||||||
|
"itemsize",
|
||||||
|
"iternext",
|
||||||
|
"keyiterator",
|
||||||
|
"kwarg",
|
||||||
|
"kwargs",
|
||||||
|
"linearization",
|
||||||
|
"linearize",
|
||||||
|
"listcomp",
|
||||||
|
"mappingproxy",
|
||||||
|
"maxsplit",
|
||||||
|
"memoryview",
|
||||||
|
"memoryviewiterator",
|
||||||
|
"metaclass",
|
||||||
|
"metaclasses",
|
||||||
|
"metatype",
|
||||||
|
"mro",
|
||||||
|
"mros",
|
||||||
|
"nanj",
|
||||||
|
"ndigits",
|
||||||
|
"ndim",
|
||||||
|
"nonbytes",
|
||||||
|
"origname",
|
||||||
|
"posixsubprocess",
|
||||||
|
"pyexpat",
|
||||||
|
"PYTHONDEBUG",
|
||||||
|
"PYTHONHOME",
|
||||||
|
"PYTHONINSPECT",
|
||||||
|
"PYTHONOPTIMIZE",
|
||||||
|
"PYTHONPATH",
|
||||||
|
"PYTHONPATH",
|
||||||
|
"PYTHONVERBOSE",
|
||||||
|
"PYTHONWARNINGS",
|
||||||
|
"qualname",
|
||||||
|
"radd",
|
||||||
|
"rdiv",
|
||||||
|
"rdivmod",
|
||||||
|
"reconstructor",
|
||||||
|
"reversevalueiterator",
|
||||||
|
"rfloordiv",
|
||||||
|
"rlshift",
|
||||||
|
"rmod",
|
||||||
|
"rpow",
|
||||||
|
"rrshift",
|
||||||
|
"rsub",
|
||||||
|
"rtruediv",
|
||||||
|
"scproxy",
|
||||||
|
"setattro",
|
||||||
|
"setcomp",
|
||||||
|
"showwarnmsg",
|
||||||
|
"warnmsg",
|
||||||
|
"stacklevel",
|
||||||
|
"subclasscheck",
|
||||||
|
"subclasshook",
|
||||||
|
"unionable",
|
||||||
|
"unraisablehook",
|
||||||
|
"valueiterator",
|
||||||
|
"vararg",
|
||||||
|
"varargs",
|
||||||
|
"varnames",
|
||||||
|
"warningregistry",
|
||||||
"warnopts",
|
"warnopts",
|
||||||
"weakproxy",
|
"weakproxy",
|
||||||
"mappingproxy",
|
"xopts",
|
||||||
// RustPython
|
// RustPython
|
||||||
"RustPython",
|
"baseclass",
|
||||||
|
"Bytecode",
|
||||||
|
"cfgs",
|
||||||
|
"codegen",
|
||||||
|
"dedentations",
|
||||||
|
"dedents",
|
||||||
|
"deduped",
|
||||||
|
"downcasted",
|
||||||
|
"dumpable",
|
||||||
|
"GetSet",
|
||||||
|
"internable",
|
||||||
|
"makeunicodedata",
|
||||||
|
"miri",
|
||||||
|
"notrace",
|
||||||
|
"pyarg",
|
||||||
"pyarg",
|
"pyarg",
|
||||||
"pyargs",
|
"pyargs",
|
||||||
"pygetset",
|
|
||||||
"pyobj",
|
|
||||||
"pystr",
|
|
||||||
"pyc",
|
|
||||||
"pyref",
|
|
||||||
"pyslot",
|
|
||||||
"PyFunction",
|
|
||||||
"PyMethod",
|
|
||||||
"PyClassMethod",
|
|
||||||
"PyStaticMethod",
|
|
||||||
"PyProperty",
|
|
||||||
"PyClass",
|
|
||||||
"pyimpl",
|
|
||||||
"pyarg",
|
|
||||||
"PyModule",
|
|
||||||
"PyAttr",
|
"PyAttr",
|
||||||
"PyResult",
|
"pyc",
|
||||||
"PyObject",
|
"PyClass",
|
||||||
|
"PyClassMethod",
|
||||||
"PyException",
|
"PyException",
|
||||||
"GetSet",
|
"PyFunction",
|
||||||
"zelf",
|
"pygetset",
|
||||||
"wasi",
|
"pyimpl",
|
||||||
"Bytecode",
|
"pymember",
|
||||||
|
"PyMethod",
|
||||||
|
"PyModule",
|
||||||
|
"pyname",
|
||||||
|
"pyobj",
|
||||||
|
"PyObject",
|
||||||
|
"pypayload",
|
||||||
|
"PyProperty",
|
||||||
|
"pyref",
|
||||||
|
"PyResult",
|
||||||
|
"pyslot",
|
||||||
|
"PyStaticMethod",
|
||||||
|
"pystr",
|
||||||
|
"pystruct",
|
||||||
|
"pystructseq",
|
||||||
|
"pytrace",
|
||||||
|
"reducelib",
|
||||||
"richcompare",
|
"richcompare",
|
||||||
"makeunicodedata",
|
"RustPython",
|
||||||
"unhashable",
|
"struc",
|
||||||
"unraisable",
|
"tracebacks",
|
||||||
"typealiases",
|
"typealiases",
|
||||||
"Unconstructible",
|
"Unconstructible",
|
||||||
"posonlyargs",
|
"unhashable",
|
||||||
"kwonlyargs",
|
|
||||||
"uninit",
|
"uninit",
|
||||||
"miri"
|
"unraisable",
|
||||||
|
"wasi",
|
||||||
|
"zelf",
|
||||||
|
// cpython
|
||||||
|
"argtypes",
|
||||||
|
"asdl",
|
||||||
|
"asname",
|
||||||
|
"augassign",
|
||||||
|
"badsyntax",
|
||||||
|
"basetype",
|
||||||
|
"boolop",
|
||||||
|
"bxor",
|
||||||
|
"cellarg",
|
||||||
|
"cellvar",
|
||||||
|
"cellvars",
|
||||||
|
"cmpop",
|
||||||
|
"dictoffset",
|
||||||
|
"elts",
|
||||||
|
"excepthandler",
|
||||||
|
"finalbody",
|
||||||
|
"freevar",
|
||||||
|
"freevars",
|
||||||
|
"fromlist",
|
||||||
|
"heaptype",
|
||||||
|
"IMMUTABLETYPE",
|
||||||
|
"kwonlyarg",
|
||||||
|
"kwonlyargs",
|
||||||
|
"linearise",
|
||||||
|
"maxdepth",
|
||||||
|
"mult",
|
||||||
|
"nkwargs",
|
||||||
|
"orelse",
|
||||||
|
"patma",
|
||||||
|
"posonlyarg",
|
||||||
|
"posonlyargs",
|
||||||
|
"prec",
|
||||||
|
"stackdepth",
|
||||||
|
"unaryop",
|
||||||
|
"unparse",
|
||||||
|
"unparser",
|
||||||
|
"VARKEYWORDS",
|
||||||
|
"varkwarg",
|
||||||
|
"wbits",
|
||||||
|
"withitem",
|
||||||
|
"withs"
|
||||||
],
|
],
|
||||||
// flagWords - list of words to be always considered incorrect
|
// flagWords - list of words to be always considered incorrect
|
||||||
"flagWords": [
|
"flagWords": [
|
||||||
|
|||||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,8 +1,6 @@
|
|||||||
Lib/** linguist-vendored
|
Lib/** linguist-vendored
|
||||||
Cargo.lock linguist-generated -merge
|
Cargo.lock linguist-generated -merge
|
||||||
*.snap linguist-generated -merge
|
*.snap linguist-generated -merge
|
||||||
ast/src/ast_gen.rs linguist-generated -merge
|
|
||||||
vm/src/stdlib/ast/gen.rs linguist-generated -merge
|
vm/src/stdlib/ast/gen.rs linguist-generated -merge
|
||||||
compiler/parser/python.lalrpop text eol=LF
|
|
||||||
Lib/*.py text working-tree-encoding=UTF-8 eol=LF
|
Lib/*.py text working-tree-encoding=UTF-8 eol=LF
|
||||||
**/*.rs text working-tree-encoding=UTF-8 eol=LF
|
**/*.rs text working-tree-encoding=UTF-8 eol=LF
|
||||||
|
|||||||
16
.github/ISSUE_TEMPLATE/empty.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/empty.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Generic issue template
|
||||||
|
about: which is not covered by other templates
|
||||||
|
title: ''
|
||||||
|
labels:
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- Short description of the issue. -->
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
<!-- Whatever you want to share -->
|
||||||
16
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Request a feature to use RustPython (as a Rust library)
|
||||||
|
title: ''
|
||||||
|
labels: C-enhancement
|
||||||
|
assignees: 'youknowone'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- Short description of the request. Please use incompatibility form to report missing features as Python interpreter -->
|
||||||
|
|
||||||
|
## Expected use case
|
||||||
|
|
||||||
|
<!-- By sharing detailed use case, we can understand the requirements better! If it will be used by open source projects, please also share the project URL. -->
|
||||||
24
.github/ISSUE_TEMPLATE/report-bug.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/report-bug.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Report bugs
|
||||||
|
about: Report a bug not related to CPython compatibility
|
||||||
|
title: ''
|
||||||
|
labels: C-bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- Short description of the bug -->
|
||||||
|
|
||||||
|
## Expected
|
||||||
|
|
||||||
|
<!-- What's the expected result? Using ``` ``` block is preferred for text. -->
|
||||||
|
|
||||||
|
## Actual
|
||||||
|
|
||||||
|
<!-- What's the actual result? Using ``` ``` block is preferred for text. -->
|
||||||
|
|
||||||
|
## Python Documentation
|
||||||
|
|
||||||
|
<!-- If applicable. -->
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
name: Report incompatibility
|
name: Report incompatibility
|
||||||
about: Report an incompatibility between RustPython and CPython
|
about: Report an incompatibility between RustPython and CPython
|
||||||
title: ''
|
title: ''
|
||||||
labels: feat
|
labels: C-compat
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -11,6 +11,6 @@ assignees: ''
|
|||||||
|
|
||||||
<!-- What Python feature is missing from RustPython? Give a short description of the feature and how you ran into its absence. -->
|
<!-- What Python feature is missing from RustPython? Give a short description of the feature and how you ran into its absence. -->
|
||||||
|
|
||||||
## Python Documentation
|
## Python Documentation or reference to CPython source code
|
||||||
|
|
||||||
<!-- Give a link to the feature in the CPython documentation (https://docs.python.org/3/) in order to assist in its implementation. -->
|
<!-- Give a link to the feature in the CPython documentation (https://docs.python.org/3/) in order to assist in its implementation. -->
|
||||||
|
|||||||
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Keep GitHub Actions up to date with GitHub's Dependabot...
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*" # Group all Actions updates into a single larger pull request
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
23
.github/workflows/ai-review.yml
vendored
Normal file
23
.github/workflows/ai-review.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: PR Review
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: AI Code Review
|
||||||
|
uses: gitea-actions/ai-reviewer@v0.6
|
||||||
|
with:
|
||||||
|
access-token: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
full-context-model: "gpt-4o"
|
||||||
|
full-context-api-key: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
single-chunk-model: "claude-3-5-sonnet"
|
||||||
|
single-chunk-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
exclude-files: "*.md,*.yaml"
|
||||||
276
.github/workflows/ci.yaml
vendored
276
.github/workflows/ci.yaml
vendored
@@ -2,7 +2,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main, release]
|
branches: [main, release]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [labeled, unlabeled, opened, synchronize, reopened]
|
types: [unlabeled, opened, synchronize, reopened]
|
||||||
|
merge_group:
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
@@ -14,18 +15,24 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,sqlite,ssl
|
||||||
NON_WASM_PACKAGES: >-
|
# Skip additional tests on Windows. They are checked on Linux and MacOS.
|
||||||
-p rustpython-common
|
WINDOWS_SKIPS: >-
|
||||||
-p rustpython-compiler-core
|
test_datetime
|
||||||
-p rustpython-compiler
|
test_glob
|
||||||
-p rustpython-codegen
|
test_importlib
|
||||||
-p rustpython-parser
|
test_io
|
||||||
-p rustpython-vm
|
test_os
|
||||||
-p rustpython-stdlib
|
test_pathlib
|
||||||
-p rustpython-jit
|
test_posixpath
|
||||||
-p rustpython-derive
|
test_venv
|
||||||
-p rustpython
|
# configparser: https://github.com/RustPython/RustPython/issues/4995#issuecomment-1582397417
|
||||||
|
# socketserver: seems related to configparser crash.
|
||||||
|
MACOS_SKIPS: >-
|
||||||
|
test_configparser
|
||||||
|
test_socketserver
|
||||||
|
# PLATFORM_INDEPENDENT_TESTS are tests that do not depend on the underlying OS. They are currently
|
||||||
|
# only run on Linux to speed up the CI.
|
||||||
PLATFORM_INDEPENDENT_TESTS: >-
|
PLATFORM_INDEPENDENT_TESTS: >-
|
||||||
test_argparse
|
test_argparse
|
||||||
test_array
|
test_array
|
||||||
@@ -97,6 +104,8 @@ env:
|
|||||||
test_unpack
|
test_unpack
|
||||||
test_weakref
|
test_weakref
|
||||||
test_yield_from
|
test_yield_from
|
||||||
|
# Python version targeted by the CI.
|
||||||
|
PYTHON_VERSION: "3.12.3"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
rust_tests:
|
rust_tests:
|
||||||
@@ -104,41 +113,47 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
name: Run rust tests
|
name: Run rust tests
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Set up the Windows environment
|
- name: Set up the Windows environment
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
choco install llvm openssl
|
cargo install --target-dir=target -v cargo-vcpkg
|
||||||
echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >>$GITHUB_ENV
|
cargo vcpkg -v build
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
- name: Set up the Mac environment
|
- name: Set up the Mac environment
|
||||||
run: brew install autoconf automake libtool
|
run: brew install autoconf automake libtool
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
|
|
||||||
- name: run clippy
|
- name: run clippy
|
||||||
run: cargo clippy ${{ env.CARGO_ARGS }} ${{ env.NON_WASM_PACKAGES }} -- -Dwarnings
|
run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --exclude rustpython_wasm -- -Dwarnings
|
||||||
|
|
||||||
- name: run rust tests
|
- name: run rust tests
|
||||||
run: cargo test --workspace --exclude rustpython_wasm --verbose --features threading ${{ env.CARGO_ARGS }} ${{ env.NON_WASM_PACKAGES }}
|
run: cargo test --workspace --exclude rustpython_wasm --verbose --features threading ${{ env.CARGO_ARGS }}
|
||||||
|
if: runner.os != 'macOS'
|
||||||
|
- name: run rust tests
|
||||||
|
run: cargo test --workspace --exclude rustpython_wasm --exclude rustpython-jit --verbose --features threading ${{ env.CARGO_ARGS }}
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
|
||||||
- name: check compilation without threading
|
- name: check compilation without threading
|
||||||
run: cargo check ${{ env.CARGO_ARGS }}
|
run: cargo check ${{ env.CARGO_ARGS }}
|
||||||
|
|
||||||
|
- name: Test example projects
|
||||||
|
run:
|
||||||
|
cargo run --manifest-path example_projects/barebone/Cargo.toml
|
||||||
|
cargo run --manifest-path example_projects/frozen_stdlib/Cargo.toml
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
|
||||||
- name: prepare AppleSilicon build
|
- name: prepare AppleSilicon build
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -159,16 +174,9 @@ jobs:
|
|||||||
exotic_targets:
|
exotic_targets:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
name: Ensure compilation on various targets
|
name: Ensure compilation on various targets
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
target: i686-unknown-linux-gnu
|
target: i686-unknown-linux-gnu
|
||||||
@@ -185,6 +193,15 @@ jobs:
|
|||||||
- name: Check compilation for android
|
- name: Check compilation for android
|
||||||
run: cargo check --target aarch64-linux-android
|
run: cargo check --target aarch64-linux-android
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
- name: Install gcc-aarch64-linux-gnu
|
||||||
|
run: sudo apt install gcc-aarch64-linux-gnu
|
||||||
|
- name: Check compilation for aarch64 linux gnu
|
||||||
|
run: cargo check --target aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
target: i686-unknown-linux-musl
|
target: i686-unknown-linux-musl
|
||||||
@@ -199,13 +216,6 @@ jobs:
|
|||||||
- name: Check compilation for freebsd
|
- name: Check compilation for freebsd
|
||||||
run: cargo check --target x86_64-unknown-freebsd
|
run: cargo check --target x86_64-unknown-freebsd
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
target: wasm32-unknown-unknown
|
|
||||||
|
|
||||||
- name: Check compilation for wasm32
|
|
||||||
run: cargo check --target wasm32-unknown-unknown --no-default-features
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
target: x86_64-unknown-freebsd
|
target: x86_64-unknown-freebsd
|
||||||
@@ -216,14 +226,13 @@ jobs:
|
|||||||
- name: Prepare repository for redox compilation
|
- name: Prepare repository for redox compilation
|
||||||
run: bash scripts/redox/uncomment-cargo.sh
|
run: bash scripts/redox/uncomment-cargo.sh
|
||||||
- name: Check compilation for Redox
|
- name: Check compilation for Redox
|
||||||
if: false # FIXME: redoxer toolchain is from ~july 2021, edition2021 isn't stabilized
|
|
||||||
uses: coolreader18/redoxer-action@v1
|
uses: coolreader18/redoxer-action@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
|
args: --ignore-rust-version
|
||||||
|
|
||||||
snippets_cpython:
|
snippets_cpython:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
needs: lalrpop
|
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
name: Run snippets and cpython tests
|
name: Run snippets and cpython tests
|
||||||
@@ -233,32 +242,30 @@ jobs:
|
|||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: actions/setup-python@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: Set up the Windows environment
|
- name: Set up the Windows environment
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
choco install llvm openssl
|
cargo install cargo-vcpkg
|
||||||
echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >>$GITHUB_ENV
|
cargo vcpkg build
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
- name: Set up the Mac environment
|
- name: Set up the Mac environment
|
||||||
run: brew install autoconf automake libtool
|
run: brew install autoconf automake libtool openssl@3
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
- name: build rustpython
|
- name: build rustpython
|
||||||
run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }}
|
run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }}
|
||||||
- uses: actions/setup-python@v2
|
if: runner.os == 'macOS'
|
||||||
|
- name: build rustpython
|
||||||
|
run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }},jit
|
||||||
|
if: runner.os != 'macOS'
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: run snippets
|
- name: run snippets
|
||||||
run: python -m pip install -r requirements.txt && pytest -v
|
run: python -m pip install -r requirements.txt && pytest -v
|
||||||
working-directory: ./extra_tests
|
working-directory: ./extra_tests
|
||||||
@@ -266,113 +273,72 @@ jobs:
|
|||||||
name: run cpython platform-independent tests
|
name: run cpython platform-independent tests
|
||||||
run:
|
run:
|
||||||
target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v ${{ env.PLATFORM_INDEPENDENT_TESTS }}
|
target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v ${{ env.PLATFORM_INDEPENDENT_TESTS }}
|
||||||
- if: runner.os != 'Windows'
|
- if: runner.os == 'Linux'
|
||||||
name: run cpython platform-dependent tests
|
name: run cpython platform-dependent tests (Linux)
|
||||||
run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }}
|
run: target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }}
|
||||||
|
- if: runner.os == 'macOS'
|
||||||
|
name: run cpython platform-dependent tests (MacOS)
|
||||||
|
run: target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.MACOS_SKIPS }}
|
||||||
- if: runner.os == 'Windows'
|
- if: runner.os == 'Windows'
|
||||||
name: run cpython platform-dependent tests (windows partial - fixme)
|
name: run cpython platform-dependent tests (windows partial - fixme)
|
||||||
run:
|
run:
|
||||||
target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }}
|
target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.WINDOWS_SKIPS }}
|
||||||
test_glob
|
|
||||||
test_importlib
|
|
||||||
test_io
|
|
||||||
test_iter
|
|
||||||
test_os
|
|
||||||
test_pathlib
|
|
||||||
test_posixpath
|
|
||||||
test_shutil
|
|
||||||
test_venv
|
|
||||||
- if: runner.os != 'Windows'
|
- if: runner.os != 'Windows'
|
||||||
name: check that --install-pip succeeds
|
name: check that --install-pip succeeds
|
||||||
run: |
|
run: |
|
||||||
mkdir site-packages
|
mkdir site-packages
|
||||||
target/release/rustpython --install-pip ensurepip --user
|
target/release/rustpython --install-pip ensurepip --user
|
||||||
|
target/release/rustpython -m pip install six
|
||||||
lalrpop:
|
- if: runner.os != 'Windows'
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
name: Check that ensurepip succeeds.
|
||||||
name: Generate parser with lalrpop
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- name: Check if cached generated parser exists
|
|
||||||
id: generated_parser
|
|
||||||
uses: andstor/file-existence-action@v1
|
|
||||||
with:
|
|
||||||
files: "compiler/parser/python.rs"
|
|
||||||
- if: runner.os == 'Windows'
|
|
||||||
name: Force python.lalrpop to be lf # actions@checkout ignore .gitattributes
|
|
||||||
run: |
|
run: |
|
||||||
set file compiler/parser/python.lalrpop; ((Get-Content $file) -join "`n") + "`n" | Set-Content -NoNewline $file
|
target/release/rustpython -m ensurepip
|
||||||
- name: Install lalrpop
|
target/release/rustpython -c "import pip"
|
||||||
if: steps.generated_parser.outputs.files_exists == 'false'
|
- if: runner.os != 'Windows'
|
||||||
uses: baptiste0928/cargo-install@v1
|
name: Check if pip inside venv is functional
|
||||||
with:
|
run: |
|
||||||
crate: lalrpop
|
target/release/rustpython -m venv testvenv
|
||||||
version: "0.19.8"
|
testvenv/bin/rustpython -m pip install wheel
|
||||||
- name: Run lalrpop
|
- name: Check whats_left is not broken
|
||||||
if: steps.generated_parser.outputs.files_exists == 'false'
|
run: python -I whats_left.py
|
||||||
run: lalrpop compiler/parser/python.lalrpop
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Check Rust code with rustfmt and clippy
|
name: Check Rust code with rustfmt and clippy
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- name: run rustfmt
|
- name: run rustfmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --check
|
||||||
- name: run clippy on wasm
|
- name: run clippy on wasm
|
||||||
run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings
|
run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: install flake8
|
- name: install ruff
|
||||||
run: python -m pip install flake8
|
run: python -m pip install ruff==0.0.291 # astral-sh/ruff#7778
|
||||||
- name: run lint
|
- name: run python lint
|
||||||
run: flake8 . --count --exclude=./.*,./Lib,./vm/Lib,./benches/ --select=E9,F63,F7,F82 --show-source --statistics
|
run: ruff extra_tests wasm examples --exclude='./.*',./Lib,./vm/Lib,./benches/ --select=E9,F63,F7,F82 --show-source
|
||||||
- name: install prettier
|
- name: install prettier
|
||||||
run: yarn global add prettier && echo "$(yarn global bin)" >>$GITHUB_PATH
|
run: yarn global add prettier && echo "$(yarn global bin)" >>$GITHUB_PATH
|
||||||
- name: check wasm code with prettier
|
- name: check wasm code with prettier
|
||||||
# prettier doesn't handle ignore files very well: https://github.com/prettier/prettier/issues/8506
|
# prettier doesn't handle ignore files very well: https://github.com/prettier/prettier/issues/8506
|
||||||
run: cd wasm && git ls-files -z | xargs -0 prettier --check -u
|
run: cd wasm && git ls-files -z | xargs -0 prettier --check -u
|
||||||
- name: Check update_asdl.sh consistency
|
|
||||||
run: bash scripts/update_asdl.sh && git diff --exit-code
|
|
||||||
- name: Check whats_left is not broken
|
|
||||||
run: python -I whats_left.py
|
|
||||||
|
|
||||||
miri:
|
miri:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
name: Run tests under miri
|
name: Run tests under miri
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
components: miri
|
components: miri
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Run tests under miri
|
- name: Run tests under miri
|
||||||
# miri-ignore-leaks because the type-object circular reference means that there will always be
|
# miri-ignore-leaks because the type-object circular reference means that there will always be
|
||||||
# a memory leak, at least until we have proper cyclic gc
|
# a memory leak, at least until we have proper cyclic gc
|
||||||
@@ -381,46 +347,53 @@ jobs:
|
|||||||
wasm:
|
wasm:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
name: Check the WASM package and demo
|
name: Check the WASM package and demo
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: install wasm-pack
|
- name: install wasm-pack
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
- name: install geckodriver
|
- name: install geckodriver
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/mozilla/geckodriver/releases/download/v0.30.0/geckodriver-v0.30.0-linux64.tar.gz
|
wget https://github.com/mozilla/geckodriver/releases/download/v0.34.0/geckodriver-v0.34.0-linux64.tar.gz
|
||||||
mkdir geckodriver
|
mkdir geckodriver
|
||||||
tar -xzf geckodriver-v0.30.0-linux64.tar.gz -C geckodriver
|
tar -xzf geckodriver-v0.34.0-linux64.tar.gz -C geckodriver
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- run: python -m pip install -r requirements.txt
|
- run: python -m pip install -r requirements.txt
|
||||||
working-directory: ./wasm/tests
|
working-directory: ./wasm/tests
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v4
|
||||||
- name: run test
|
- name: run test
|
||||||
run: |
|
run: |
|
||||||
export PATH=$PATH:`pwd`/../../geckodriver
|
export PATH=$PATH:`pwd`/../../geckodriver
|
||||||
npm install
|
npm install
|
||||||
npm run test
|
npm run test
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||||
working-directory: ./wasm/demo
|
working-directory: ./wasm/demo
|
||||||
|
- uses: mwilliamson/setup-wabt-action@v3
|
||||||
|
with: { wabt-version: "1.0.30" }
|
||||||
|
- name: check wasm32-unknown without js
|
||||||
|
run: |
|
||||||
|
cargo build --release --manifest-path wasm/wasm-unknown-test/Cargo.toml --target wasm32-unknown-unknown --verbose
|
||||||
|
if wasm-objdump -xj Import target/wasm32-unknown-unknown/release/wasm_unknown_test.wasm; then
|
||||||
|
echo "ERROR: wasm32-unknown module expects imports from the host environment" >2
|
||||||
|
fi
|
||||||
- name: build notebook demo
|
- name: build notebook demo
|
||||||
if: github.ref == 'refs/heads/release'
|
if: github.ref == 'refs/heads/release'
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
npm run dist
|
npm run dist
|
||||||
mv dist ../demo/dist/notebook
|
mv dist ../demo/dist/notebook
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||||
working-directory: ./wasm/notebook
|
working-directory: ./wasm/notebook
|
||||||
- name: Deploy demo to Github Pages
|
- name: Deploy demo to Github Pages
|
||||||
if: success() && github.ref == 'refs/heads/release'
|
if: success() && github.ref == 'refs/heads/release'
|
||||||
uses: peaceiris/actions-gh-pages@v2
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
env:
|
env:
|
||||||
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||||
PUBLISH_DIR: ./wasm/demo/dist
|
PUBLISH_DIR: ./wasm/demo/dist
|
||||||
@@ -430,24 +403,21 @@ jobs:
|
|||||||
wasm-wasi:
|
wasm-wasi:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
name: Run snippets and cpython tests on wasm-wasi
|
name: Run snippets and cpython tests on wasm-wasi
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
target: wasm32-wasi
|
target: wasm32-wasip1
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Setup Wasmer
|
- name: Setup Wasmer
|
||||||
uses: wasmerio/setup-wasmer@v1
|
uses: wasmerio/setup-wasmer@v3
|
||||||
- name: Install clang
|
- name: Install clang
|
||||||
run: sudo apt-get update && sudo apt-get install clang -y
|
run: sudo apt-get update && sudo apt-get install clang -y
|
||||||
- name: build rustpython
|
- name: build rustpython
|
||||||
run: cargo build --release --target wasm32-wasi --features freeze-stdlib,stdlib --verbose
|
run: cargo build --release --target wasm32-wasip1 --features freeze-stdlib,stdlib --verbose
|
||||||
- name: run snippets
|
- name: run snippets
|
||||||
run: wasmer run --dir . target/wasm32-wasi/release/rustpython.wasm -- extra_tests/snippets/stdlib_random.py
|
run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/extra_tests/snippets/stdlib_random.py
|
||||||
|
- name: run cpython unittest
|
||||||
|
run: wasmer run --dir `pwd` target/wasm32-wasip1/release/rustpython.wasm -- `pwd`/Lib/test/test_int.py
|
||||||
|
|||||||
119
.github/workflows/cron-ci.yaml
vendored
119
.github/workflows/cron-ci.yaml
vendored
@@ -6,71 +6,43 @@ on:
|
|||||||
name: Periodic checks/tasks
|
name: Periodic checks/tasks
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_ARGS: --features ssl,jit
|
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
||||||
|
PYTHON_VERSION: "3.12.0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
||||||
|
# This is done using cargo-llvm-cov, which is a wrapper around llvm-cov.
|
||||||
codecov:
|
codecov:
|
||||||
name: Collect code coverage data
|
name: Collect code coverage data
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: taiki-e/install-action@cargo-llvm-cov
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
components: llvm-tools-preview
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- run: sudo apt-get update && sudo apt-get -y install lcov
|
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||||
- run: cargo build --release --verbose ${{ env.CARGO_ARGS }}
|
- name: Run cargo-llvm-cov with Rust tests.
|
||||||
env:
|
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --verbose --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
||||||
RUSTC_WRAPPER: './scripts/codecoverage-rustc-wrapper.sh'
|
- name: Run cargo-llvm-cov with Python snippets.
|
||||||
- uses: actions/setup-python@v2
|
run: python scripts/cargo-llvm-cov.py
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
- run: python -m pip install pytest
|
|
||||||
working-directory: ./extra_tests
|
|
||||||
- name: run snippets
|
|
||||||
run: LLVM_PROFILE_FILE="$PWD/snippet-%p.profraw" pytest -v
|
|
||||||
working-directory: ./extra_tests
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: run cpython tests
|
- name: Run cargo-llvm-cov with Python test suite.
|
||||||
run: |
|
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
|
||||||
alltests=($(target/release/rustpython -c 'from test.libregrtest.runtest import findtests; print(*findtests())'))
|
|
||||||
i=0
|
|
||||||
# chunk into chunks of 10 tests each. idk at this point
|
|
||||||
while subtests=("${alltests[@]:$i:10}"); [[ ${#subtests[@]} -ne 0 ]]; do
|
|
||||||
LLVM_PROFILE_FILE="$PWD/regrtest-%p.profraw" target/release/rustpython -m test -v "${subtests[@]}" || true
|
|
||||||
((i+=10))
|
|
||||||
done
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: prepare code coverage data
|
- name: Prepare code coverage data
|
||||||
run: |
|
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
|
||||||
rusttool() {
|
- name: Upload to Codecov
|
||||||
local tool=$1; shift; "$(rustc --print target-libdir)/../bin/llvm-$tool" "$@"
|
uses: codecov/codecov-action@v5
|
||||||
}
|
|
||||||
rusttool profdata merge extra_tests/snippet-*.profraw regrtest-*.profraw --output codecov.profdata
|
|
||||||
rusttool cov export --instr-profile codecov.profdata target/release/rustpython --format lcov > codecov_tmp.lcov
|
|
||||||
lcov -e codecov_tmp.lcov "$PWD"/'*' -o codecov_tmp2.lcov
|
|
||||||
lcov -r codecov_tmp2.lcov "$PWD"/target/'*' -o codecov.lcov # remove LALRPOP-generated parser
|
|
||||||
- name: upload to Codecov
|
|
||||||
uses: codecov/codecov-action@v3
|
|
||||||
with:
|
with:
|
||||||
file: ./codecov.lcov
|
file: ./codecov.lcov
|
||||||
|
|
||||||
testdata:
|
testdata:
|
||||||
name: Collect regression test data
|
name: Collect regression test data
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: build rustpython
|
- name: build rustpython
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
@@ -97,16 +69,13 @@ jobs:
|
|||||||
|
|
||||||
whatsleft:
|
whatsleft:
|
||||||
name: Collect what is left data
|
name: Collect what is left data
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
- name: build rustpython
|
- name: build rustpython
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
- name: Collect what is left data
|
- name: Collect what is left data
|
||||||
@@ -135,17 +104,11 @@ jobs:
|
|||||||
|
|
||||||
benchmark:
|
benchmark:
|
||||||
name: Collect benchmark data
|
name: Collect benchmark data
|
||||||
needs: lalrpop
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- run: cargo install cargo-criterion
|
- run: cargo install cargo-criterion
|
||||||
@@ -183,35 +146,3 @@ jobs:
|
|||||||
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update benchmark results"; then
|
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update benchmark results"; then
|
||||||
git push
|
git push
|
||||||
fi
|
fi
|
||||||
|
|
||||||
lalrpop:
|
|
||||||
name: Generate parser with lalrpop
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Cache generated parser
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: compiler/parser/python.rs
|
|
||||||
key: lalrpop-${{ hashFiles('compiler/parser/python.lalrpop') }}
|
|
||||||
- name: Check if cached generated parser exists
|
|
||||||
id: generated_parser
|
|
||||||
uses: andstor/file-existence-action@v1
|
|
||||||
with:
|
|
||||||
files: "compiler/parser/python.rs"
|
|
||||||
- if: runner.os == 'Windows'
|
|
||||||
name: Force python.lalrpop to be lf # actions@checkout ignore .gitattributes
|
|
||||||
run: |
|
|
||||||
set file compiler/parser/python.lalrpop; ((Get-Content $file) -join "`n") + "`n" | Set-Content -NoNewline $file
|
|
||||||
- name: Install lalrpop
|
|
||||||
if: steps.generated_parser.outputs.files_exists == 'false'
|
|
||||||
uses: baptiste0928/cargo-install@v1
|
|
||||||
with:
|
|
||||||
crate: lalrpop
|
|
||||||
version: "0.19.8"
|
|
||||||
- name: Run lalrpop
|
|
||||||
if: steps.generated_parser.outputs.files_exists == 'false'
|
|
||||||
run: lalrpop compiler/parser/python.lalrpop
|
|
||||||
|
|||||||
145
.github/workflows/release.yml
vendored
Normal file
145
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# 9 AM UTC on every Monday
|
||||||
|
- cron: "0 9 * * Mon"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pre-release:
|
||||||
|
type: boolean
|
||||||
|
description: Mark "Pre-Release"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,sqlite,ssl
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.platform.runner }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- runner: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
# - runner: ubuntu-latest
|
||||||
|
# target: i686-unknown-linux-gnu
|
||||||
|
# - runner: ubuntu-latest
|
||||||
|
# target: aarch64-unknown-linux-gnu
|
||||||
|
# - runner: ubuntu-latest
|
||||||
|
# target: armv7-unknown-linux-gnueabi
|
||||||
|
# - runner: ubuntu-latest
|
||||||
|
# target: s390x-unknown-linux-gnu
|
||||||
|
# - runner: ubuntu-latest
|
||||||
|
# target: powerpc64le-unknown-linux-gnu
|
||||||
|
- runner: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
# - runner: macos-latest
|
||||||
|
# target: x86_64-apple-darwin
|
||||||
|
- runner: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
# - runner: windows-latest
|
||||||
|
# target: i686-pc-windows-msvc
|
||||||
|
# - runner: windows-latest
|
||||||
|
# target: aarch64-pc-windows-msvc
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Set up Environment
|
||||||
|
shell: bash
|
||||||
|
run: rustup target add ${{ matrix.platform.target }}
|
||||||
|
- name: Set up Windows Environment
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo install --target-dir=target -v cargo-vcpkg
|
||||||
|
cargo vcpkg -v build
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
- name: Set up MacOS Environment
|
||||||
|
run: brew install autoconf automake libtool
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
|
||||||
|
- name: Build RustPython
|
||||||
|
run: cargo build --release --target=${{ matrix.platform.target }} --verbose --features=threading ${{ env.CARGO_ARGS }}
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
- name: Build RustPython
|
||||||
|
run: cargo build --release --target=${{ matrix.platform.target }} --verbose --features=threading ${{ env.CARGO_ARGS }},jit
|
||||||
|
if: runner.os != 'macOS'
|
||||||
|
|
||||||
|
- name: Rename Binary
|
||||||
|
run: cp target/${{ matrix.platform.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
|
||||||
|
if: runner.os != 'Windows'
|
||||||
|
- name: Rename Binary
|
||||||
|
run: cp target/${{ matrix.platform.target }}/release/rustpython.exe target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}.exe
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
|
||||||
|
- name: Upload Binary Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
|
||||||
|
path: target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}*
|
||||||
|
|
||||||
|
build-wasm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Set up Environment
|
||||||
|
shell: bash
|
||||||
|
run: rustup target add wasm32-wasip1
|
||||||
|
|
||||||
|
- name: Build RustPython
|
||||||
|
run: cargo build --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib --release
|
||||||
|
|
||||||
|
- name: Rename Binary
|
||||||
|
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
|
||||||
|
|
||||||
|
- name: Upload Binary Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rustpython-release-wasm32-wasip1
|
||||||
|
path: target/rustpython-release-wasm32-wasip1.wasm
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build, build-wasm]
|
||||||
|
steps:
|
||||||
|
- name: Download Binary Artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: bin
|
||||||
|
pattern: rustpython-release-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: List Binaries
|
||||||
|
run: |
|
||||||
|
ls -lah bin/
|
||||||
|
file bin/*
|
||||||
|
- name: Create Release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ github.ref_name }}
|
||||||
|
run: ${{ github.run_number }}
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event.inputs.pre-release }}" == "false" ]]; then
|
||||||
|
RELEASE_TYPE_NAME=Release
|
||||||
|
PRERELEASE_ARG=
|
||||||
|
else
|
||||||
|
RELEASE_TYPE_NAME=Pre-Release
|
||||||
|
PRERELEASE_ARG=--prerelease
|
||||||
|
fi
|
||||||
|
|
||||||
|
today=$(date '+%Y-%m-%d')
|
||||||
|
gh release create "$today-$tag-$run" \
|
||||||
|
--repo="$GITHUB_REPOSITORY" \
|
||||||
|
--title="RustPython $RELEASE_TYPE_NAME $today-$tag #$run" \
|
||||||
|
--target="$tag" \
|
||||||
|
--generate-notes \
|
||||||
|
$PRERELEASE_ARG \
|
||||||
|
bin/rustpython-release-*
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,6 +9,8 @@ __pycache__
|
|||||||
.vscode
|
.vscode
|
||||||
wasm-pack.log
|
wasm-pack.log
|
||||||
.idea/
|
.idea/
|
||||||
|
.envrc
|
||||||
|
.python-version
|
||||||
|
|
||||||
flame-graph.html
|
flame-graph.html
|
||||||
flame.txt
|
flame.txt
|
||||||
@@ -19,5 +21,3 @@ flamescope.json
|
|||||||
|
|
||||||
extra_tests/snippets/resources
|
extra_tests/snippets/resources
|
||||||
extra_tests/not_impl.py
|
extra_tests/not_impl.py
|
||||||
|
|
||||||
compiler/parser/python.rs
|
|
||||||
|
|||||||
21
.vscode/launch.json
vendored
21
.vscode/launch.json
vendored
@@ -8,15 +8,24 @@
|
|||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug executable 'rustpython'",
|
"name": "Debug executable 'rustpython'",
|
||||||
"cargo": {
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"--package=rustpython"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"preLaunchTask": "Build RustPython Debug",
|
"preLaunchTask": "Build RustPython Debug",
|
||||||
"program": "target/debug/rustpython",
|
"program": "target/debug/rustpython",
|
||||||
"args": [],
|
"args": [],
|
||||||
|
"env": {
|
||||||
|
"RUST_BACKTRACE": "1"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'rustpython' without SSL",
|
||||||
|
"preLaunchTask": "Build RustPython Debug without SSL",
|
||||||
|
"program": "target/debug/rustpython",
|
||||||
|
"args": [],
|
||||||
|
"env": {
|
||||||
|
"RUST_BACKTRACE": "1"
|
||||||
|
},
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
20
.vscode/tasks.json
vendored
20
.vscode/tasks.json
vendored
@@ -2,7 +2,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Build RustPython Debug",
|
"label": "Build RustPython Debug without SSL",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cargo",
|
"command": "cargo",
|
||||||
"args": [
|
"args": [
|
||||||
@@ -15,6 +15,22 @@
|
|||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true,
|
"isDefault": true,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"label": "Build RustPython Debug",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cargo",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--features=ssl"
|
||||||
|
],
|
||||||
|
"problemMatcher": [
|
||||||
|
"$rustc",
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
1851
Cargo.lock
generated
1851
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
176
Cargo.toml
176
Cargo.toml
@@ -1,62 +1,54 @@
|
|||||||
# REDOX START
|
|
||||||
# cargo-features = ["edition2021"]
|
|
||||||
# REDOX END
|
|
||||||
[package]
|
[package]
|
||||||
name = "rustpython"
|
name = "rustpython"
|
||||||
version = "0.2.0"
|
|
||||||
authors = ["RustPython Team"]
|
|
||||||
edition = "2021"
|
|
||||||
description = "A python interpreter written in rust."
|
description = "A python interpreter written in rust."
|
||||||
repository = "https://github.com/RustPython/RustPython"
|
|
||||||
license = "MIT"
|
|
||||||
include = ["LICENSE", "Cargo.toml", "src/**/*.rs"]
|
include = ["LICENSE", "Cargo.toml", "src/**/*.rs"]
|
||||||
|
version.workspace = true
|
||||||
[workspace]
|
authors.workspace = true
|
||||||
resolver = "2"
|
edition.workspace = true
|
||||||
members = [
|
rust-version.workspace = true
|
||||||
"compiler", "compiler/ast", "compiler/core", "compiler/codegen", "compiler/parser",
|
repository.workspace = true
|
||||||
".", "common", "derive", "jit", "vm", "pylib", "stdlib", "wasm/lib", "derive-impl",
|
license.workspace = true
|
||||||
]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["threading", "stdlib", "zlib", "importlib", "encodings", "rustpython-parser/lalrpop"]
|
default = ["threading", "stdlib", "zlib", "importlib"]
|
||||||
importlib = ["rustpython-vm/importlib"]
|
importlib = ["rustpython-vm/importlib"]
|
||||||
encodings = ["rustpython-vm/encodings"]
|
encodings = ["rustpython-vm/encodings"]
|
||||||
stdlib = ["rustpython-stdlib", "rustpython-pylib"]
|
stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"]
|
||||||
flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"]
|
flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"]
|
||||||
freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
|
freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
|
||||||
jit = ["rustpython-vm/jit"]
|
jit = ["rustpython-vm/jit"]
|
||||||
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
||||||
zlib = ["stdlib", "rustpython-stdlib/zlib"]
|
zlib = ["stdlib", "rustpython-stdlib/zlib"]
|
||||||
bz2 = ["stdlib", "rustpython-stdlib/bz2"]
|
bz2 = ["stdlib", "rustpython-stdlib/bz2"]
|
||||||
|
sqlite = ["rustpython-stdlib/sqlite"]
|
||||||
ssl = ["rustpython-stdlib/ssl"]
|
ssl = ["rustpython-stdlib/ssl"]
|
||||||
ssl-vendor = ["rustpython-stdlib/ssl-vendor"]
|
ssl-vendor = ["ssl", "rustpython-stdlib/ssl-vendor"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustpython-compiler = { path = "compiler", version = "0.2.0" }
|
rustpython-compiler = { workspace = true }
|
||||||
rustpython-parser = { path = "compiler/parser", version = "0.2.0" }
|
rustpython-pylib = { workspace = true, optional = true }
|
||||||
rustpython-pylib = { path = "pylib", optional = true, default-features = false }
|
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
|
||||||
rustpython-stdlib = { path = "stdlib", optional = true, default-features = false }
|
rustpython-vm = { workspace = true, features = ["compiler"] }
|
||||||
rustpython-vm = { path = "vm", version = "0.2.0", default-features = false, features = ["compiler"] }
|
rustpython-parser = { workspace = true }
|
||||||
|
|
||||||
|
cfg-if = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
flame = { workspace = true, optional = true }
|
||||||
|
|
||||||
cfg-if = "1.0.0"
|
|
||||||
clap = "2.34"
|
clap = "2.34"
|
||||||
dirs = { package = "dirs-next", version = "2.0.0" }
|
dirs = { package = "dirs-next", version = "2.0.0" }
|
||||||
env_logger = { version = "0.9.0", default-features = false, features = ["atty", "termcolor"] }
|
env_logger = { version = "0.9.0", default-features = false, features = ["atty", "termcolor"] }
|
||||||
flame = { version = "0.2.2", optional = true }
|
|
||||||
flamescope = { version = "0.1.2", optional = true }
|
flamescope = { version = "0.1.2", optional = true }
|
||||||
libc = "0.2.133"
|
|
||||||
log = "0.4.16"
|
[target.'cfg(windows)'.dependencies]
|
||||||
num-traits = "0.2.14"
|
libc = { workspace = true }
|
||||||
atty = "0.2.14"
|
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
rustyline = "10.0.0"
|
rustyline = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
cpython = "0.7.0"
|
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||||
criterion = "0.3.5"
|
pyo3 = { version = "0.22", features = ["auto-initialize"] }
|
||||||
python3-sys = "0.7.0"
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "execution"
|
name = "execution"
|
||||||
@@ -87,5 +79,119 @@ opt-level = 3
|
|||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# REDOX START, Uncommment when you want to compile/check with redoxer
|
# REDOX START, Uncomment when you want to compile/check with redoxer
|
||||||
# REDOX END
|
# REDOX END
|
||||||
|
|
||||||
|
# Used only on Windows to build the vcpkg dependencies
|
||||||
|
[package.metadata.vcpkg]
|
||||||
|
git = "https://github.com/microsoft/vcpkg"
|
||||||
|
# The revision of the vcpkg repository to use
|
||||||
|
# https://github.com/microsoft/vcpkg/tags
|
||||||
|
rev = "2024.02.14"
|
||||||
|
|
||||||
|
[package.metadata.vcpkg.target]
|
||||||
|
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", dev-dependencies = ["openssl" ] }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"compiler", "compiler/core", "compiler/codegen",
|
||||||
|
".", "common", "derive", "jit", "vm", "vm/sre_engine", "pylib", "stdlib", "derive-impl",
|
||||||
|
"wasm/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.4.0"
|
||||||
|
authors = ["RustPython Team"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.83.0"
|
||||||
|
repository = "https://github.com/RustPython/RustPython"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
rustpython-compiler-core = { path = "compiler/core", version = "0.4.0" }
|
||||||
|
rustpython-compiler = { path = "compiler", version = "0.4.0" }
|
||||||
|
rustpython-codegen = { path = "compiler/codegen", version = "0.4.0" }
|
||||||
|
rustpython-common = { path = "common", version = "0.4.0" }
|
||||||
|
rustpython-derive = { path = "derive", version = "0.4.0" }
|
||||||
|
rustpython-derive-impl = { path = "derive-impl", version = "0.4.0" }
|
||||||
|
rustpython-jit = { path = "jit", version = "0.4.0" }
|
||||||
|
rustpython-vm = { path = "vm", default-features = false, version = "0.4.0" }
|
||||||
|
rustpython-pylib = { path = "pylib", version = "0.4.0" }
|
||||||
|
rustpython-stdlib = { path = "stdlib", default-features = false, version = "0.4.0" }
|
||||||
|
rustpython-sre_engine = { path = "vm/sre_engine", version = "0.4.0" }
|
||||||
|
rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" }
|
||||||
|
|
||||||
|
# rustpython-literal = { version = "0.4.0" }
|
||||||
|
# rustpython-parser-core = { version = "0.4.0" }
|
||||||
|
# rustpython-parser = { version = "0.4.0" }
|
||||||
|
# rustpython-ast = { version = "0.4.0" }
|
||||||
|
# rustpython-format= { version = "0.4.0" }
|
||||||
|
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||||
|
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||||
|
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||||
|
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||||
|
rustpython-format = { git = "https://github.com/RustPython/Parser.git", version = "0.4.0", rev = "4588ea5c3e6327009640e7c9c89eb6fa9220358e" }
|
||||||
|
# rustpython-literal = { path = "../RustPython-parser/literal" }
|
||||||
|
# rustpython-parser-core = { path = "../RustPython-parser/core" }
|
||||||
|
# rustpython-parser = { path = "../RustPython-parser/parser" }
|
||||||
|
# rustpython-ast = { path = "../RustPython-parser/ast" }
|
||||||
|
# rustpython-format = { path = "../RustPython-parser/format" }
|
||||||
|
|
||||||
|
ahash = "0.8.11"
|
||||||
|
ascii = "1.0"
|
||||||
|
bitflags = "2.4.1"
|
||||||
|
bstr = "1"
|
||||||
|
cfg-if = "1.0"
|
||||||
|
chrono = "0.4.37"
|
||||||
|
crossbeam-utils = "0.8.19"
|
||||||
|
flame = "0.2.2"
|
||||||
|
getrandom = "0.2.12"
|
||||||
|
glob = "0.3"
|
||||||
|
hex = "0.4.3"
|
||||||
|
indexmap = { version = "2.2.6", features = ["std"] }
|
||||||
|
insta = "1.38.0"
|
||||||
|
itertools = "0.11.0"
|
||||||
|
is-macro = "0.3.0"
|
||||||
|
junction = "1.0.0"
|
||||||
|
libc = "0.2.153"
|
||||||
|
log = "0.4.16"
|
||||||
|
nix = { version = "0.29", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
|
||||||
|
malachite-bigint = "0.2.2"
|
||||||
|
malachite-q = "<=0.4.18"
|
||||||
|
malachite-base = "<=0.4.18"
|
||||||
|
memchr = "2.7.2"
|
||||||
|
num-complex = "0.4.0"
|
||||||
|
num-integer = "0.1.44"
|
||||||
|
num-traits = "0.2"
|
||||||
|
num_enum = { version = "0.7", default-features = false }
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
parking_lot = "0.12.1"
|
||||||
|
paste = "1.0.7"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rustix = { version = "0.38", features = ["event"] }
|
||||||
|
rustyline = "14.0.0"
|
||||||
|
serde = { version = "1.0.133", default-features = false }
|
||||||
|
schannel = "0.1.22"
|
||||||
|
static_assertions = "1.1"
|
||||||
|
strum = "0.26"
|
||||||
|
strum_macros = "0.26"
|
||||||
|
syn = "1.0.109"
|
||||||
|
thiserror = "1.0"
|
||||||
|
thread_local = "1.1.4"
|
||||||
|
unicode_names2 = "1.2.0"
|
||||||
|
widestring = "1.1.0"
|
||||||
|
windows-sys = "0.52.0"
|
||||||
|
wasm-bindgen = "0.2.92"
|
||||||
|
|
||||||
|
# Lints
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
unsafe_code = "allow"
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
perf = "warn"
|
||||||
|
style = "warn"
|
||||||
|
complexity = "warn"
|
||||||
|
suspicious = "warn"
|
||||||
|
correctness = "warn"
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ The contents of the Development Guide include:
|
|||||||
|
|
||||||
RustPython requires the following:
|
RustPython requires the following:
|
||||||
|
|
||||||
- Rust latest stable version (e.g 1.51.0 as of Apr 2 2021)
|
- Rust latest stable version (e.g 1.69.0 as of Apr 20 2023)
|
||||||
- To check Rust version: `rustc --version`
|
- To check Rust version: `rustc --version`
|
||||||
- If you have `rustup` on your system, enter to update to the latest
|
- If you have `rustup` on your system, enter to update to the latest
|
||||||
stable version: `rustup update stable`
|
stable version: `rustup update stable`
|
||||||
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
|
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
|
||||||
do so.
|
do so.
|
||||||
- CPython version 3.10 or higher
|
- CPython version 3.12 or higher
|
||||||
- CPython can be installed by your operating system's package manager,
|
- CPython can be installed by your operating system's package manager,
|
||||||
from the [Python website](https://www.python.org/downloads/), or
|
from the [Python website](https://www.python.org/downloads/), or
|
||||||
using a third-party distribution, such as
|
using a third-party distribution, such as
|
||||||
@@ -47,7 +47,10 @@ you can check yourself with `cargo clippy`.
|
|||||||
|
|
||||||
Custom Python code (i.e. code not copied from CPython's standard library) should
|
Custom Python code (i.e. code not copied from CPython's standard library) should
|
||||||
follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) style. We also use
|
follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) style. We also use
|
||||||
[flake8](http://flake8.pycqa.org/en/latest/) to check Python code style.
|
[ruff](https://beta.ruff.rs/docs/) to check Python code style.
|
||||||
|
|
||||||
|
In addition to language specific tools, [cspell](https://github.com/streetsidesoftware/cspell),
|
||||||
|
a code spell checker, is used in order to ensure correct spellings for code.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|||||||
6
Lib/__future__.py
vendored
6
Lib/__future__.py
vendored
@@ -33,7 +33,7 @@ in releases at or after that, modules no longer need
|
|||||||
to use the feature in question, but may continue to use such imports.
|
to use the feature in question, but may continue to use such imports.
|
||||||
|
|
||||||
MandatoryRelease may also be None, meaning that a planned feature got
|
MandatoryRelease may also be None, meaning that a planned feature got
|
||||||
dropped.
|
dropped or that the release version is undetermined.
|
||||||
|
|
||||||
Instances of class _Feature have two corresponding methods,
|
Instances of class _Feature have two corresponding methods,
|
||||||
.getOptionalRelease() and .getMandatoryRelease().
|
.getOptionalRelease() and .getMandatoryRelease().
|
||||||
@@ -96,7 +96,7 @@ class _Feature:
|
|||||||
"""Return release in which this feature will become mandatory.
|
"""Return release in which this feature will become mandatory.
|
||||||
|
|
||||||
This is a 5-tuple, of the same form as sys.version_info, or, if
|
This is a 5-tuple, of the same form as sys.version_info, or, if
|
||||||
the feature was dropped, is None.
|
the feature was dropped, or the release date is undetermined, is None.
|
||||||
"""
|
"""
|
||||||
return self.mandatory
|
return self.mandatory
|
||||||
|
|
||||||
@@ -143,5 +143,5 @@ generator_stop = _Feature((3, 5, 0, "beta", 1),
|
|||||||
CO_FUTURE_GENERATOR_STOP)
|
CO_FUTURE_GENERATOR_STOP)
|
||||||
|
|
||||||
annotations = _Feature((3, 7, 0, "beta", 1),
|
annotations = _Feature((3, 7, 0, "beta", 1),
|
||||||
(3, 11, 0, "alpha", 0),
|
None,
|
||||||
CO_FUTURE_ANNOTATIONS)
|
CO_FUTURE_ANNOTATIONS)
|
||||||
|
|||||||
16
Lib/__hello__.py
vendored
Normal file
16
Lib/__hello__.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
initialized = True
|
||||||
|
|
||||||
|
class TestFrozenUtf8_1:
|
||||||
|
"""\u00b6"""
|
||||||
|
|
||||||
|
class TestFrozenUtf8_2:
|
||||||
|
"""\u03c0"""
|
||||||
|
|
||||||
|
class TestFrozenUtf8_4:
|
||||||
|
"""\U0001f600"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Hello world!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
7
Lib/__phello__/__init__.py
vendored
Normal file
7
Lib/__phello__/__init__.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
initialized = True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Hello world!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
7
Lib/__phello__/spam.py
vendored
Normal file
7
Lib/__phello__/spam.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
initialized = True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Hello world!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
132
Lib/_collections_abc.py
vendored
132
Lib/_collections_abc.py
vendored
@@ -6,6 +6,32 @@
|
|||||||
Unit tests are in test_collections.
|
Unit tests are in test_collections.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
############ Maintenance notes #########################################
|
||||||
|
#
|
||||||
|
# ABCs are different from other standard library modules in that they
|
||||||
|
# specify compliance tests. In general, once an ABC has been published,
|
||||||
|
# new methods (either abstract or concrete) cannot be added.
|
||||||
|
#
|
||||||
|
# Though classes that inherit from an ABC would automatically receive a
|
||||||
|
# new mixin method, registered classes would become non-compliant and
|
||||||
|
# violate the contract promised by ``isinstance(someobj, SomeABC)``.
|
||||||
|
#
|
||||||
|
# Though irritating, the correct procedure for adding new abstract or
|
||||||
|
# mixin methods is to create a new ABC as a subclass of the previous
|
||||||
|
# ABC. For example, union(), intersection(), and difference() cannot
|
||||||
|
# be added to Set but could go into a new ABC that extends Set.
|
||||||
|
#
|
||||||
|
# Because they are so hard to change, new ABCs should have their APIs
|
||||||
|
# carefully thought through prior to publication.
|
||||||
|
#
|
||||||
|
# Since ABCMeta only checks for the presence of methods, it is possible
|
||||||
|
# to alter the signature of a method by adding optional arguments
|
||||||
|
# or changing parameters names. This is still a bit dubious but at
|
||||||
|
# least it won't cause isinstance() to return an incorrect result.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -23,7 +49,7 @@ __all__ = ["Awaitable", "Coroutine",
|
|||||||
"Mapping", "MutableMapping",
|
"Mapping", "MutableMapping",
|
||||||
"MappingView", "KeysView", "ItemsView", "ValuesView",
|
"MappingView", "KeysView", "ItemsView", "ValuesView",
|
||||||
"Sequence", "MutableSequence",
|
"Sequence", "MutableSequence",
|
||||||
"ByteString",
|
"ByteString", "Buffer",
|
||||||
]
|
]
|
||||||
|
|
||||||
# This module has been renamed from collections.abc to _collections_abc to
|
# This module has been renamed from collections.abc to _collections_abc to
|
||||||
@@ -413,6 +439,21 @@ class Collection(Sized, Iterable, Container):
|
|||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
class Buffer(metaclass=ABCMeta):
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def __buffer__(self, flags: int, /) -> memoryview:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __subclasshook__(cls, C):
|
||||||
|
if cls is Buffer:
|
||||||
|
return _check_methods(C, "__buffer__")
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
class _CallableGenericAlias(GenericAlias):
|
class _CallableGenericAlias(GenericAlias):
|
||||||
""" Represent `Callable[argtypes, resulttype]`.
|
""" Represent `Callable[argtypes, resulttype]`.
|
||||||
|
|
||||||
@@ -430,25 +471,13 @@ class _CallableGenericAlias(GenericAlias):
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Callable must be used as Callable[[arg, ...], result].")
|
"Callable must be used as Callable[[arg, ...], result].")
|
||||||
t_args, t_result = args
|
t_args, t_result = args
|
||||||
if isinstance(t_args, list):
|
if isinstance(t_args, (tuple, list)):
|
||||||
args = (*t_args, t_result)
|
args = (*t_args, t_result)
|
||||||
elif not _is_param_expr(t_args):
|
elif not _is_param_expr(t_args):
|
||||||
raise TypeError(f"Expected a list of types, an ellipsis, "
|
raise TypeError(f"Expected a list of types, an ellipsis, "
|
||||||
f"ParamSpec, or Concatenate. Got {t_args}")
|
f"ParamSpec, or Concatenate. Got {t_args}")
|
||||||
return super().__new__(cls, origin, args)
|
return super().__new__(cls, origin, args)
|
||||||
|
|
||||||
@property
|
|
||||||
def __parameters__(self):
|
|
||||||
params = []
|
|
||||||
for arg in self.__args__:
|
|
||||||
# Looks like a genericalias
|
|
||||||
if hasattr(arg, "__parameters__") and isinstance(arg.__parameters__, tuple):
|
|
||||||
params.extend(arg.__parameters__)
|
|
||||||
else:
|
|
||||||
if _is_typevarlike(arg):
|
|
||||||
params.append(arg)
|
|
||||||
return tuple(dict.fromkeys(params))
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if len(self.__args__) == 2 and _is_param_expr(self.__args__[0]):
|
if len(self.__args__) == 2 and _is_param_expr(self.__args__[0]):
|
||||||
return super().__repr__()
|
return super().__repr__()
|
||||||
@@ -467,55 +496,18 @@ class _CallableGenericAlias(GenericAlias):
|
|||||||
# rather than the default types.GenericAlias object. Most of the
|
# rather than the default types.GenericAlias object. Most of the
|
||||||
# code is copied from typing's _GenericAlias and the builtin
|
# code is copied from typing's _GenericAlias and the builtin
|
||||||
# types.GenericAlias.
|
# types.GenericAlias.
|
||||||
|
|
||||||
# A special case in PEP 612 where if X = Callable[P, int],
|
|
||||||
# then X[int, str] == X[[int, str]].
|
|
||||||
param_len = len(self.__parameters__)
|
|
||||||
if param_len == 0:
|
|
||||||
raise TypeError(f'{self} is not a generic class')
|
|
||||||
if not isinstance(item, tuple):
|
if not isinstance(item, tuple):
|
||||||
item = (item,)
|
item = (item,)
|
||||||
if (param_len == 1 and _is_param_expr(self.__parameters__[0])
|
|
||||||
and item and not _is_param_expr(item[0])):
|
new_args = super().__getitem__(item).__args__
|
||||||
item = (list(item),)
|
|
||||||
item_len = len(item)
|
|
||||||
if item_len != param_len:
|
|
||||||
raise TypeError(f'Too {"many" if item_len > param_len else "few"}'
|
|
||||||
f' arguments for {self};'
|
|
||||||
f' actual {item_len}, expected {param_len}')
|
|
||||||
subst = dict(zip(self.__parameters__, item))
|
|
||||||
new_args = []
|
|
||||||
for arg in self.__args__:
|
|
||||||
if _is_typevarlike(arg):
|
|
||||||
if _is_param_expr(arg):
|
|
||||||
arg = subst[arg]
|
|
||||||
if not _is_param_expr(arg):
|
|
||||||
raise TypeError(f"Expected a list of types, an ellipsis, "
|
|
||||||
f"ParamSpec, or Concatenate. Got {arg}")
|
|
||||||
else:
|
|
||||||
arg = subst[arg]
|
|
||||||
# Looks like a GenericAlias
|
|
||||||
elif hasattr(arg, '__parameters__') and isinstance(arg.__parameters__, tuple):
|
|
||||||
subparams = arg.__parameters__
|
|
||||||
if subparams:
|
|
||||||
subargs = tuple(subst[x] for x in subparams)
|
|
||||||
arg = arg[subargs]
|
|
||||||
new_args.append(arg)
|
|
||||||
|
|
||||||
# args[0] occurs due to things like Z[[int, str, bool]] from PEP 612
|
# args[0] occurs due to things like Z[[int, str, bool]] from PEP 612
|
||||||
if not isinstance(new_args[0], list):
|
if not isinstance(new_args[0], (tuple, list)):
|
||||||
t_result = new_args[-1]
|
t_result = new_args[-1]
|
||||||
t_args = new_args[:-1]
|
t_args = new_args[:-1]
|
||||||
new_args = (t_args, t_result)
|
new_args = (t_args, t_result)
|
||||||
return _CallableGenericAlias(Callable, tuple(new_args))
|
return _CallableGenericAlias(Callable, tuple(new_args))
|
||||||
|
|
||||||
|
|
||||||
def _is_typevarlike(arg):
|
|
||||||
obj = type(arg)
|
|
||||||
# looks like a TypeVar/ParamSpec
|
|
||||||
return (obj.__module__ == 'typing'
|
|
||||||
and obj.__name__ in {'ParamSpec', 'TypeVar'})
|
|
||||||
|
|
||||||
def _is_param_expr(obj):
|
def _is_param_expr(obj):
|
||||||
"""Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or
|
"""Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or
|
||||||
``_ConcatenateGenericAlias`` from typing.py
|
``_ConcatenateGenericAlias`` from typing.py
|
||||||
@@ -533,9 +525,8 @@ def _type_repr(obj):
|
|||||||
|
|
||||||
Copied from :mod:`typing` since collections.abc
|
Copied from :mod:`typing` since collections.abc
|
||||||
shouldn't depend on that module.
|
shouldn't depend on that module.
|
||||||
|
(Keep this roughly in sync with the typing version.)
|
||||||
"""
|
"""
|
||||||
if isinstance(obj, GenericAlias):
|
|
||||||
return repr(obj)
|
|
||||||
if isinstance(obj, type):
|
if isinstance(obj, type):
|
||||||
if obj.__module__ == 'builtins':
|
if obj.__module__ == 'builtins':
|
||||||
return obj.__qualname__
|
return obj.__qualname__
|
||||||
@@ -868,7 +859,7 @@ class KeysView(MappingView, Set):
|
|||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_iterable(self, it):
|
def _from_iterable(cls, it):
|
||||||
return set(it)
|
return set(it)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -886,7 +877,7 @@ class ItemsView(MappingView, Set):
|
|||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_iterable(self, it):
|
def _from_iterable(cls, it):
|
||||||
return set(it)
|
return set(it)
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
@@ -1064,10 +1055,10 @@ class Sequence(Reversible, Collection):
|
|||||||
while stop is None or i < stop:
|
while stop is None or i < stop:
|
||||||
try:
|
try:
|
||||||
v = self[i]
|
v = self[i]
|
||||||
if v is value or v == value:
|
|
||||||
return i
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
break
|
break
|
||||||
|
if v is value or v == value:
|
||||||
|
return i
|
||||||
i += 1
|
i += 1
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
@@ -1080,8 +1071,27 @@ Sequence.register(str)
|
|||||||
Sequence.register(range)
|
Sequence.register(range)
|
||||||
Sequence.register(memoryview)
|
Sequence.register(memoryview)
|
||||||
|
|
||||||
|
class _DeprecateByteStringMeta(ABCMeta):
|
||||||
|
def __new__(cls, name, bases, namespace, **kwargs):
|
||||||
|
if name != "ByteString":
|
||||||
|
import warnings
|
||||||
|
|
||||||
class ByteString(Sequence):
|
warnings._deprecated(
|
||||||
|
"collections.abc.ByteString",
|
||||||
|
remove=(3, 14),
|
||||||
|
)
|
||||||
|
return super().__new__(cls, name, bases, namespace, **kwargs)
|
||||||
|
|
||||||
|
def __instancecheck__(cls, instance):
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings._deprecated(
|
||||||
|
"collections.abc.ByteString",
|
||||||
|
remove=(3, 14),
|
||||||
|
)
|
||||||
|
return super().__instancecheck__(instance)
|
||||||
|
|
||||||
|
class ByteString(Sequence, metaclass=_DeprecateByteStringMeta):
|
||||||
"""This unifies bytes and bytearray.
|
"""This unifies bytes and bytearray.
|
||||||
|
|
||||||
XXX Should add all their methods.
|
XXX Should add all their methods.
|
||||||
|
|||||||
12
Lib/_compression.py
vendored
12
Lib/_compression.py
vendored
@@ -1,7 +1,7 @@
|
|||||||
"""Internal classes used by the gzip, lzma and bz2 modules"""
|
"""Internal classes used by the gzip, lzma and bz2 modules"""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import sys
|
||||||
|
|
||||||
BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size
|
BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size
|
||||||
|
|
||||||
@@ -110,6 +110,16 @@ class DecompressReader(io.RawIOBase):
|
|||||||
self._pos += len(data)
|
self._pos += len(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def readall(self):
|
||||||
|
chunks = []
|
||||||
|
# sys.maxsize means the max length of output buffer is unlimited,
|
||||||
|
# so that the whole input buffer can be decompressed within one
|
||||||
|
# .decompress() call.
|
||||||
|
while data := self.read(sys.maxsize):
|
||||||
|
chunks.append(data)
|
||||||
|
|
||||||
|
return b"".join(chunks)
|
||||||
|
|
||||||
# Rewind the file to the beginning of the data stream.
|
# Rewind the file to the beginning of the data stream.
|
||||||
def _rewind(self):
|
def _rewind(self):
|
||||||
self._fp.seek(0)
|
self._fp.seek(0)
|
||||||
|
|||||||
22
Lib/_dummy_os.py
vendored
22
Lib/_dummy_os.py
vendored
@@ -5,22 +5,30 @@ A shim of the os module containing only simple path-related utilities
|
|||||||
try:
|
try:
|
||||||
from os import *
|
from os import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import abc
|
import abc, sys
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
raise OSError("no os specific module found")
|
if name in {"_path_normpath", "__path__"}:
|
||||||
|
raise AttributeError(name)
|
||||||
|
if name.isupper():
|
||||||
|
return 0
|
||||||
|
def dummy(*args, **kwargs):
|
||||||
|
import io
|
||||||
|
return io.UnsupportedOperation(f"{name}: no os specific module found")
|
||||||
|
dummy.__name__ = f"dummy_{name}"
|
||||||
|
return dummy
|
||||||
|
|
||||||
def _shim():
|
sys.modules['os'] = sys.modules['posix'] = sys.modules[__name__]
|
||||||
import _dummy_os, sys
|
|
||||||
sys.modules['os'] = _dummy_os
|
|
||||||
sys.modules['os.path'] = _dummy_os.path
|
|
||||||
|
|
||||||
import posixpath as path
|
import posixpath as path
|
||||||
import sys
|
|
||||||
sys.modules['os.path'] = path
|
sys.modules['os.path'] = path
|
||||||
del sys
|
del sys
|
||||||
|
|
||||||
sep = path.sep
|
sep = path.sep
|
||||||
|
supports_dir_fd = set()
|
||||||
|
supports_effective_ids = set()
|
||||||
|
supports_fd = set()
|
||||||
|
supports_follow_symlinks = set()
|
||||||
|
|
||||||
|
|
||||||
def fspath(path):
|
def fspath(path):
|
||||||
|
|||||||
3
Lib/_dummy_thread.py
vendored
3
Lib/_dummy_thread.py
vendored
@@ -145,6 +145,9 @@ class LockType(object):
|
|||||||
def locked(self):
|
def locked(self):
|
||||||
return self.locked_status
|
return self.locked_status
|
||||||
|
|
||||||
|
def _at_fork_reinit(self):
|
||||||
|
self.locked_status = False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %s.%s object at %s>" % (
|
return "<%s %s.%s object at %s>" % (
|
||||||
"locked" if self.locked_status else "unlocked",
|
"locked" if self.locked_status else "unlocked",
|
||||||
|
|||||||
35
Lib/_markupbase.py
vendored
35
Lib/_markupbase.py
vendored
@@ -29,10 +29,6 @@ class ParserBase:
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"_markupbase.ParserBase must be subclassed")
|
"_markupbase.ParserBase must be subclassed")
|
||||||
|
|
||||||
def error(self, message):
|
|
||||||
raise NotImplementedError(
|
|
||||||
"subclasses of ParserBase must override error()")
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.lineno = 1
|
self.lineno = 1
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
@@ -131,12 +127,11 @@ class ParserBase:
|
|||||||
# also in data attribute specifications of attlist declaration
|
# also in data attribute specifications of attlist declaration
|
||||||
# also link type declaration subsets in linktype declarations
|
# also link type declaration subsets in linktype declarations
|
||||||
# also link attribute specification lists in link declarations
|
# also link attribute specification lists in link declarations
|
||||||
self.error("unsupported '[' char in %s declaration" % decltype)
|
raise AssertionError("unsupported '[' char in %s declaration" % decltype)
|
||||||
else:
|
else:
|
||||||
self.error("unexpected '[' char in declaration")
|
raise AssertionError("unexpected '[' char in declaration")
|
||||||
else:
|
else:
|
||||||
self.error(
|
raise AssertionError("unexpected %r char in declaration" % rawdata[j])
|
||||||
"unexpected %r char in declaration" % rawdata[j])
|
|
||||||
if j < 0:
|
if j < 0:
|
||||||
return j
|
return j
|
||||||
return -1 # incomplete
|
return -1 # incomplete
|
||||||
@@ -156,7 +151,9 @@ class ParserBase:
|
|||||||
# look for MS Office ]> ending
|
# look for MS Office ]> ending
|
||||||
match= _msmarkedsectionclose.search(rawdata, i+3)
|
match= _msmarkedsectionclose.search(rawdata, i+3)
|
||||||
else:
|
else:
|
||||||
self.error('unknown status keyword %r in marked section' % rawdata[i+3:j])
|
raise AssertionError(
|
||||||
|
'unknown status keyword %r in marked section' % rawdata[i+3:j]
|
||||||
|
)
|
||||||
if not match:
|
if not match:
|
||||||
return -1
|
return -1
|
||||||
if report:
|
if report:
|
||||||
@@ -168,7 +165,7 @@ class ParserBase:
|
|||||||
def parse_comment(self, i, report=1):
|
def parse_comment(self, i, report=1):
|
||||||
rawdata = self.rawdata
|
rawdata = self.rawdata
|
||||||
if rawdata[i:i+4] != '<!--':
|
if rawdata[i:i+4] != '<!--':
|
||||||
self.error('unexpected call to parse_comment()')
|
raise AssertionError('unexpected call to parse_comment()')
|
||||||
match = _commentclose.search(rawdata, i+4)
|
match = _commentclose.search(rawdata, i+4)
|
||||||
if not match:
|
if not match:
|
||||||
return -1
|
return -1
|
||||||
@@ -192,7 +189,9 @@ class ParserBase:
|
|||||||
return -1
|
return -1
|
||||||
if s != "<!":
|
if s != "<!":
|
||||||
self.updatepos(declstartpos, j + 1)
|
self.updatepos(declstartpos, j + 1)
|
||||||
self.error("unexpected char in internal subset (in %r)" % s)
|
raise AssertionError(
|
||||||
|
"unexpected char in internal subset (in %r)" % s
|
||||||
|
)
|
||||||
if (j + 2) == n:
|
if (j + 2) == n:
|
||||||
# end of buffer; incomplete
|
# end of buffer; incomplete
|
||||||
return -1
|
return -1
|
||||||
@@ -209,8 +208,9 @@ class ParserBase:
|
|||||||
return -1
|
return -1
|
||||||
if name not in {"attlist", "element", "entity", "notation"}:
|
if name not in {"attlist", "element", "entity", "notation"}:
|
||||||
self.updatepos(declstartpos, j + 2)
|
self.updatepos(declstartpos, j + 2)
|
||||||
self.error(
|
raise AssertionError(
|
||||||
"unknown declaration %r in internal subset" % name)
|
"unknown declaration %r in internal subset" % name
|
||||||
|
)
|
||||||
# handle the individual names
|
# handle the individual names
|
||||||
meth = getattr(self, "_parse_doctype_" + name)
|
meth = getattr(self, "_parse_doctype_" + name)
|
||||||
j = meth(j, declstartpos)
|
j = meth(j, declstartpos)
|
||||||
@@ -234,14 +234,14 @@ class ParserBase:
|
|||||||
if rawdata[j] == ">":
|
if rawdata[j] == ">":
|
||||||
return j
|
return j
|
||||||
self.updatepos(declstartpos, j)
|
self.updatepos(declstartpos, j)
|
||||||
self.error("unexpected char after internal subset")
|
raise AssertionError("unexpected char after internal subset")
|
||||||
else:
|
else:
|
||||||
return -1
|
return -1
|
||||||
elif c.isspace():
|
elif c.isspace():
|
||||||
j = j + 1
|
j = j + 1
|
||||||
else:
|
else:
|
||||||
self.updatepos(declstartpos, j)
|
self.updatepos(declstartpos, j)
|
||||||
self.error("unexpected char %r in internal subset" % c)
|
raise AssertionError("unexpected char %r in internal subset" % c)
|
||||||
# end of buffer reached
|
# end of buffer reached
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
@@ -387,8 +387,9 @@ class ParserBase:
|
|||||||
return name.lower(), m.end()
|
return name.lower(), m.end()
|
||||||
else:
|
else:
|
||||||
self.updatepos(declstartpos, i)
|
self.updatepos(declstartpos, i)
|
||||||
self.error("expected name token at %r"
|
raise AssertionError(
|
||||||
% rawdata[declstartpos:declstartpos+20])
|
"expected name token at %r" % rawdata[declstartpos:declstartpos+20]
|
||||||
|
)
|
||||||
|
|
||||||
# To be overridden -- handlers for unknown objects
|
# To be overridden -- handlers for unknown objects
|
||||||
def unknown_decl(self, data):
|
def unknown_decl(self, data):
|
||||||
|
|||||||
2643
Lib/_pydatetime.py
vendored
Normal file
2643
Lib/_pydatetime.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
27
Lib/_pydecimal.py
vendored
27
Lib/_pydecimal.py
vendored
@@ -734,18 +734,23 @@ class Decimal(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(f, int): # handle integer inputs
|
if isinstance(f, int): # handle integer inputs
|
||||||
return cls(f)
|
sign = 0 if f >= 0 else 1
|
||||||
if not isinstance(f, float):
|
k = 0
|
||||||
raise TypeError("argument must be int or float.")
|
coeff = str(abs(f))
|
||||||
if _math.isinf(f) or _math.isnan(f):
|
elif isinstance(f, float):
|
||||||
return cls(repr(f))
|
if _math.isinf(f) or _math.isnan(f):
|
||||||
if _math.copysign(1.0, f) == 1.0:
|
return cls(repr(f))
|
||||||
sign = 0
|
if _math.copysign(1.0, f) == 1.0:
|
||||||
|
sign = 0
|
||||||
|
else:
|
||||||
|
sign = 1
|
||||||
|
n, d = abs(f).as_integer_ratio()
|
||||||
|
k = d.bit_length() - 1
|
||||||
|
coeff = str(n*5**k)
|
||||||
else:
|
else:
|
||||||
sign = 1
|
raise TypeError("argument must be int or float.")
|
||||||
n, d = abs(f).as_integer_ratio()
|
|
||||||
k = d.bit_length() - 1
|
result = _dec_from_triple(sign, coeff, -k)
|
||||||
result = _dec_from_triple(sign, str(n*5**k), -k)
|
|
||||||
if cls is Decimal:
|
if cls is Decimal:
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
|
|||||||
90
Lib/_pyio.py
vendored
90
Lib/_pyio.py
vendored
@@ -44,8 +44,9 @@ def text_encoding(encoding, stacklevel=2):
|
|||||||
"""
|
"""
|
||||||
A helper function to choose the text encoding.
|
A helper function to choose the text encoding.
|
||||||
|
|
||||||
When encoding is not None, just return it.
|
When encoding is not None, this function returns it.
|
||||||
Otherwise, return the default text encoding (i.e. "locale").
|
Otherwise, this function returns the default text encoding
|
||||||
|
(i.e. "locale" or "utf-8" depends on UTF-8 mode).
|
||||||
|
|
||||||
This function emits an EncodingWarning if *encoding* is None and
|
This function emits an EncodingWarning if *encoding* is None and
|
||||||
sys.flags.warn_default_encoding is true.
|
sys.flags.warn_default_encoding is true.
|
||||||
@@ -55,7 +56,10 @@ def text_encoding(encoding, stacklevel=2):
|
|||||||
However, please consider using encoding="utf-8" for new APIs.
|
However, please consider using encoding="utf-8" for new APIs.
|
||||||
"""
|
"""
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
encoding = "locale"
|
if sys.flags.utf8_mode:
|
||||||
|
encoding = "utf-8"
|
||||||
|
else:
|
||||||
|
encoding = "locale"
|
||||||
if sys.flags.warn_default_encoding:
|
if sys.flags.warn_default_encoding:
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn("'encoding' argument not specified.",
|
warnings.warn("'encoding' argument not specified.",
|
||||||
@@ -101,7 +105,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
|||||||
'b' binary mode
|
'b' binary mode
|
||||||
't' text mode (default)
|
't' text mode (default)
|
||||||
'+' open a disk file for updating (reading and writing)
|
'+' open a disk file for updating (reading and writing)
|
||||||
'U' universal newline mode (deprecated)
|
|
||||||
========= ===============================================================
|
========= ===============================================================
|
||||||
|
|
||||||
The default mode is 'rt' (open for reading text). For binary random
|
The default mode is 'rt' (open for reading text). For binary random
|
||||||
@@ -117,10 +120,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
|||||||
returned as strings, the bytes having been first decoded using a
|
returned as strings, the bytes having been first decoded using a
|
||||||
platform-dependent encoding or using the specified encoding if given.
|
platform-dependent encoding or using the specified encoding if given.
|
||||||
|
|
||||||
'U' mode is deprecated and will raise an exception in future versions
|
|
||||||
of Python. It has no effect in Python 3. Use newline to control
|
|
||||||
universal newlines mode.
|
|
||||||
|
|
||||||
buffering is an optional integer used to set the buffering policy.
|
buffering is an optional integer used to set the buffering policy.
|
||||||
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
|
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
|
||||||
line buffering (only usable in text mode), and an integer > 1 to indicate
|
line buffering (only usable in text mode), and an integer > 1 to indicate
|
||||||
@@ -206,7 +205,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
|||||||
if errors is not None and not isinstance(errors, str):
|
if errors is not None and not isinstance(errors, str):
|
||||||
raise TypeError("invalid errors: %r" % errors)
|
raise TypeError("invalid errors: %r" % errors)
|
||||||
modes = set(mode)
|
modes = set(mode)
|
||||||
if modes - set("axrwb+tU") or len(mode) > len(modes):
|
if modes - set("axrwb+t") or len(mode) > len(modes):
|
||||||
raise ValueError("invalid mode: %r" % mode)
|
raise ValueError("invalid mode: %r" % mode)
|
||||||
creating = "x" in modes
|
creating = "x" in modes
|
||||||
reading = "r" in modes
|
reading = "r" in modes
|
||||||
@@ -215,13 +214,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
|||||||
updating = "+" in modes
|
updating = "+" in modes
|
||||||
text = "t" in modes
|
text = "t" in modes
|
||||||
binary = "b" in modes
|
binary = "b" in modes
|
||||||
if "U" in modes:
|
|
||||||
if creating or writing or appending or updating:
|
|
||||||
raise ValueError("mode U cannot be combined with 'x', 'w', 'a', or '+'")
|
|
||||||
import warnings
|
|
||||||
warnings.warn("'U' mode is deprecated",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
reading = True
|
|
||||||
if text and binary:
|
if text and binary:
|
||||||
raise ValueError("can't have text and binary mode at once")
|
raise ValueError("can't have text and binary mode at once")
|
||||||
if creating + reading + writing + appending > 1:
|
if creating + reading + writing + appending > 1:
|
||||||
@@ -311,22 +303,6 @@ except AttributeError:
|
|||||||
open_code = _open_code_with_warning
|
open_code = _open_code_with_warning
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
|
||||||
if name == "OpenWrapper":
|
|
||||||
# bpo-43680: Until Python 3.9, _pyio.open was not a static method and
|
|
||||||
# builtins.open was set to OpenWrapper to not become a bound method
|
|
||||||
# when set to a class variable. _io.open is a built-in function whereas
|
|
||||||
# _pyio.open is a Python function. In Python 3.10, _pyio.open() is now
|
|
||||||
# a static method, and builtins.open() is now io.open().
|
|
||||||
import warnings
|
|
||||||
warnings.warn('OpenWrapper is deprecated, use open instead',
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
global OpenWrapper
|
|
||||||
OpenWrapper = open
|
|
||||||
return OpenWrapper
|
|
||||||
raise AttributeError(name)
|
|
||||||
|
|
||||||
|
|
||||||
# In normal operation, both `UnsupportedOperation`s should be bound to the
|
# In normal operation, both `UnsupportedOperation`s should be bound to the
|
||||||
# same object.
|
# same object.
|
||||||
try:
|
try:
|
||||||
@@ -338,8 +314,7 @@ except AttributeError:
|
|||||||
|
|
||||||
class IOBase(metaclass=abc.ABCMeta):
|
class IOBase(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
"""The abstract base class for all I/O classes, acting on streams of
|
"""The abstract base class for all I/O classes.
|
||||||
bytes. There is no public constructor.
|
|
||||||
|
|
||||||
This class provides dummy implementations for many methods that
|
This class provides dummy implementations for many methods that
|
||||||
derived classes can override selectively; the default implementations
|
derived classes can override selectively; the default implementations
|
||||||
@@ -1154,6 +1129,7 @@ class BufferedReader(_BufferedIOMixin):
|
|||||||
do at most one raw read to satisfy it. We never return more
|
do at most one raw read to satisfy it. We never return more
|
||||||
than self.buffer_size.
|
than self.buffer_size.
|
||||||
"""
|
"""
|
||||||
|
self._checkClosed("peek of closed file")
|
||||||
with self._read_lock:
|
with self._read_lock:
|
||||||
return self._peek_unlocked(size)
|
return self._peek_unlocked(size)
|
||||||
|
|
||||||
@@ -1172,6 +1148,7 @@ class BufferedReader(_BufferedIOMixin):
|
|||||||
"""Reads up to size bytes, with at most one read() system call."""
|
"""Reads up to size bytes, with at most one read() system call."""
|
||||||
# Returns up to size bytes. If at least one byte is buffered, we
|
# Returns up to size bytes. If at least one byte is buffered, we
|
||||||
# only return buffered bytes. Otherwise, we do one raw read.
|
# only return buffered bytes. Otherwise, we do one raw read.
|
||||||
|
self._checkClosed("read of closed file")
|
||||||
if size < 0:
|
if size < 0:
|
||||||
size = self.buffer_size
|
size = self.buffer_size
|
||||||
if size == 0:
|
if size == 0:
|
||||||
@@ -1189,6 +1166,8 @@ class BufferedReader(_BufferedIOMixin):
|
|||||||
def _readinto(self, buf, read1):
|
def _readinto(self, buf, read1):
|
||||||
"""Read data into *buf* with at most one system call."""
|
"""Read data into *buf* with at most one system call."""
|
||||||
|
|
||||||
|
self._checkClosed("readinto of closed file")
|
||||||
|
|
||||||
# Need to create a memoryview object of type 'b', otherwise
|
# Need to create a memoryview object of type 'b', otherwise
|
||||||
# we may not be able to assign bytes to it, and slicing it
|
# we may not be able to assign bytes to it, and slicing it
|
||||||
# would create a new object.
|
# would create a new object.
|
||||||
@@ -1233,11 +1212,13 @@ class BufferedReader(_BufferedIOMixin):
|
|||||||
return written
|
return written
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
|
# GH-95782: Keep return value non-negative
|
||||||
|
return max(_BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos, 0)
|
||||||
|
|
||||||
def seek(self, pos, whence=0):
|
def seek(self, pos, whence=0):
|
||||||
if whence not in valid_seek_flags:
|
if whence not in valid_seek_flags:
|
||||||
raise ValueError("invalid whence value")
|
raise ValueError("invalid whence value")
|
||||||
|
self._checkClosed("seek of closed file")
|
||||||
with self._read_lock:
|
with self._read_lock:
|
||||||
if whence == 1:
|
if whence == 1:
|
||||||
pos -= len(self._read_buf) - self._read_pos
|
pos -= len(self._read_buf) - self._read_pos
|
||||||
@@ -1845,7 +1826,7 @@ class TextIOBase(IOBase):
|
|||||||
"""Base class for text I/O.
|
"""Base class for text I/O.
|
||||||
|
|
||||||
This class provides a character and line based interface to stream
|
This class provides a character and line based interface to stream
|
||||||
I/O. There is no public constructor.
|
I/O.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def read(self, size=-1):
|
def read(self, size=-1):
|
||||||
@@ -1997,7 +1978,7 @@ class TextIOWrapper(TextIOBase):
|
|||||||
r"""Character and line based layer over a BufferedIOBase object, buffer.
|
r"""Character and line based layer over a BufferedIOBase object, buffer.
|
||||||
|
|
||||||
encoding gives the name of the encoding that the stream will be
|
encoding gives the name of the encoding that the stream will be
|
||||||
decoded or encoded with. It defaults to locale.getpreferredencoding(False).
|
decoded or encoded with. It defaults to locale.getencoding().
|
||||||
|
|
||||||
errors determines the strictness of encoding and decoding (see the
|
errors determines the strictness of encoding and decoding (see the
|
||||||
codecs.register) and defaults to "strict".
|
codecs.register) and defaults to "strict".
|
||||||
@@ -2031,19 +2012,7 @@ class TextIOWrapper(TextIOBase):
|
|||||||
encoding = text_encoding(encoding)
|
encoding = text_encoding(encoding)
|
||||||
|
|
||||||
if encoding == "locale":
|
if encoding == "locale":
|
||||||
try:
|
encoding = self._get_locale_encoding()
|
||||||
encoding = os.device_encoding(buffer.fileno()) or "locale"
|
|
||||||
except (AttributeError, UnsupportedOperation):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if encoding == "locale":
|
|
||||||
try:
|
|
||||||
import locale
|
|
||||||
except ImportError:
|
|
||||||
# Importing locale may fail if Python is being built
|
|
||||||
encoding = "utf-8"
|
|
||||||
else:
|
|
||||||
encoding = locale.getpreferredencoding(False)
|
|
||||||
|
|
||||||
if not isinstance(encoding, str):
|
if not isinstance(encoding, str):
|
||||||
raise ValueError("invalid encoding: %r" % encoding)
|
raise ValueError("invalid encoding: %r" % encoding)
|
||||||
@@ -2176,6 +2145,8 @@ class TextIOWrapper(TextIOBase):
|
|||||||
else:
|
else:
|
||||||
if not isinstance(encoding, str):
|
if not isinstance(encoding, str):
|
||||||
raise TypeError("invalid encoding: %r" % encoding)
|
raise TypeError("invalid encoding: %r" % encoding)
|
||||||
|
if encoding == "locale":
|
||||||
|
encoding = self._get_locale_encoding()
|
||||||
|
|
||||||
if newline is Ellipsis:
|
if newline is Ellipsis:
|
||||||
newline = self._readnl
|
newline = self._readnl
|
||||||
@@ -2243,8 +2214,9 @@ class TextIOWrapper(TextIOBase):
|
|||||||
self.buffer.write(b)
|
self.buffer.write(b)
|
||||||
if self._line_buffering and (haslf or "\r" in s):
|
if self._line_buffering and (haslf or "\r" in s):
|
||||||
self.flush()
|
self.flush()
|
||||||
self._set_decoded_chars('')
|
if self._snapshot is not None:
|
||||||
self._snapshot = None
|
self._set_decoded_chars('')
|
||||||
|
self._snapshot = None
|
||||||
if self._decoder:
|
if self._decoder:
|
||||||
self._decoder.reset()
|
self._decoder.reset()
|
||||||
return length
|
return length
|
||||||
@@ -2280,6 +2252,15 @@ class TextIOWrapper(TextIOBase):
|
|||||||
self._decoded_chars_used += len(chars)
|
self._decoded_chars_used += len(chars)
|
||||||
return chars
|
return chars
|
||||||
|
|
||||||
|
def _get_locale_encoding(self):
|
||||||
|
try:
|
||||||
|
import locale
|
||||||
|
except ImportError:
|
||||||
|
# Importing locale may fail if Python is being built
|
||||||
|
return "utf-8"
|
||||||
|
else:
|
||||||
|
return locale.getencoding()
|
||||||
|
|
||||||
def _rewind_decoded_chars(self, n):
|
def _rewind_decoded_chars(self, n):
|
||||||
"""Rewind the _decoded_chars buffer."""
|
"""Rewind the _decoded_chars buffer."""
|
||||||
if self._decoded_chars_used < n:
|
if self._decoded_chars_used < n:
|
||||||
@@ -2549,8 +2530,9 @@ class TextIOWrapper(TextIOBase):
|
|||||||
# Read everything.
|
# Read everything.
|
||||||
result = (self._get_decoded_chars() +
|
result = (self._get_decoded_chars() +
|
||||||
decoder.decode(self.buffer.read(), final=True))
|
decoder.decode(self.buffer.read(), final=True))
|
||||||
self._set_decoded_chars('')
|
if self._snapshot is not None:
|
||||||
self._snapshot = None
|
self._set_decoded_chars('')
|
||||||
|
self._snapshot = None
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
# Keep reading chunks until we have size characters to return.
|
# Keep reading chunks until we have size characters to return.
|
||||||
|
|||||||
3
Lib/_sitebuiltins.py
vendored
3
Lib/_sitebuiltins.py
vendored
@@ -10,7 +10,6 @@ The objects used by the site module to add custom builtins.
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class Quitter(object):
|
class Quitter(object):
|
||||||
def __init__(self, name, eof):
|
def __init__(self, name, eof):
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -48,7 +47,7 @@ class _Printer(object):
|
|||||||
data = None
|
data = None
|
||||||
for filename in self.__filenames:
|
for filename in self.__filenames:
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as fp:
|
with open(filename, encoding='utf-8') as fp:
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
break
|
break
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|||||||
565
Lib/_strptime.py
vendored
Normal file
565
Lib/_strptime.py
vendored
Normal file
@@ -0,0 +1,565 @@
|
|||||||
|
"""Strptime-related classes and functions.
|
||||||
|
|
||||||
|
CLASSES:
|
||||||
|
LocaleTime -- Discovers and stores locale-specific time information
|
||||||
|
TimeRE -- Creates regexes for pattern matching a string of text containing
|
||||||
|
time information
|
||||||
|
|
||||||
|
FUNCTIONS:
|
||||||
|
_getlang -- Figure out what language is being used for the locale
|
||||||
|
strptime -- Calculates the time struct represented by the passed-in string
|
||||||
|
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import locale
|
||||||
|
import calendar
|
||||||
|
from re import compile as re_compile
|
||||||
|
from re import IGNORECASE
|
||||||
|
from re import escape as re_escape
|
||||||
|
from datetime import (date as datetime_date,
|
||||||
|
timedelta as datetime_timedelta,
|
||||||
|
timezone as datetime_timezone)
|
||||||
|
from _thread import allocate_lock as _thread_allocate_lock
|
||||||
|
|
||||||
|
__all__ = []
|
||||||
|
|
||||||
|
def _getlang():
|
||||||
|
# Figure out what the current language is set to.
|
||||||
|
return locale.getlocale(locale.LC_TIME)
|
||||||
|
|
||||||
|
class LocaleTime(object):
|
||||||
|
"""Stores and handles locale-specific information related to time.
|
||||||
|
|
||||||
|
ATTRIBUTES:
|
||||||
|
f_weekday -- full weekday names (7-item list)
|
||||||
|
a_weekday -- abbreviated weekday names (7-item list)
|
||||||
|
f_month -- full month names (13-item list; dummy value in [0], which
|
||||||
|
is added by code)
|
||||||
|
a_month -- abbreviated month names (13-item list, dummy value in
|
||||||
|
[0], which is added by code)
|
||||||
|
am_pm -- AM/PM representation (2-item list)
|
||||||
|
LC_date_time -- format string for date/time representation (string)
|
||||||
|
LC_date -- format string for date representation (string)
|
||||||
|
LC_time -- format string for time representation (string)
|
||||||
|
timezone -- daylight- and non-daylight-savings timezone representation
|
||||||
|
(2-item list of sets)
|
||||||
|
lang -- Language used by instance (2-item tuple)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Set all attributes.
|
||||||
|
|
||||||
|
Order of methods called matters for dependency reasons.
|
||||||
|
|
||||||
|
The locale language is set at the offset and then checked again before
|
||||||
|
exiting. This is to make sure that the attributes were not set with a
|
||||||
|
mix of information from more than one locale. This would most likely
|
||||||
|
happen when using threads where one thread calls a locale-dependent
|
||||||
|
function while another thread changes the locale while the function in
|
||||||
|
the other thread is still running. Proper coding would call for
|
||||||
|
locks to prevent changing the locale while locale-dependent code is
|
||||||
|
running. The check here is done in case someone does not think about
|
||||||
|
doing this.
|
||||||
|
|
||||||
|
Only other possible issue is if someone changed the timezone and did
|
||||||
|
not call tz.tzset . That is an issue for the programmer, though,
|
||||||
|
since changing the timezone is worthless without that call.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.lang = _getlang()
|
||||||
|
self.__calc_weekday()
|
||||||
|
self.__calc_month()
|
||||||
|
self.__calc_am_pm()
|
||||||
|
self.__calc_timezone()
|
||||||
|
self.__calc_date_time()
|
||||||
|
if _getlang() != self.lang:
|
||||||
|
raise ValueError("locale changed during initialization")
|
||||||
|
if time.tzname != self.tzname or time.daylight != self.daylight:
|
||||||
|
raise ValueError("timezone changed during initialization")
|
||||||
|
|
||||||
|
def __calc_weekday(self):
|
||||||
|
# Set self.a_weekday and self.f_weekday using the calendar
|
||||||
|
# module.
|
||||||
|
a_weekday = [calendar.day_abbr[i].lower() for i in range(7)]
|
||||||
|
f_weekday = [calendar.day_name[i].lower() for i in range(7)]
|
||||||
|
self.a_weekday = a_weekday
|
||||||
|
self.f_weekday = f_weekday
|
||||||
|
|
||||||
|
def __calc_month(self):
|
||||||
|
# Set self.f_month and self.a_month using the calendar module.
|
||||||
|
a_month = [calendar.month_abbr[i].lower() for i in range(13)]
|
||||||
|
f_month = [calendar.month_name[i].lower() for i in range(13)]
|
||||||
|
self.a_month = a_month
|
||||||
|
self.f_month = f_month
|
||||||
|
|
||||||
|
def __calc_am_pm(self):
|
||||||
|
# Set self.am_pm by using time.strftime().
|
||||||
|
|
||||||
|
# The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
|
||||||
|
# magical; just happened to have used it everywhere else where a
|
||||||
|
# static date was needed.
|
||||||
|
am_pm = []
|
||||||
|
for hour in (1, 22):
|
||||||
|
time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
|
||||||
|
am_pm.append(time.strftime("%p", time_tuple).lower())
|
||||||
|
self.am_pm = am_pm
|
||||||
|
|
||||||
|
def __calc_date_time(self):
|
||||||
|
# Set self.date_time, self.date, & self.time by using
|
||||||
|
# time.strftime().
|
||||||
|
|
||||||
|
# Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
|
||||||
|
# overloaded numbers is minimized. The order in which searches for
|
||||||
|
# values within the format string is very important; it eliminates
|
||||||
|
# possible ambiguity for what something represents.
|
||||||
|
time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
|
||||||
|
date_time = [None, None, None]
|
||||||
|
date_time[0] = time.strftime("%c", time_tuple).lower()
|
||||||
|
date_time[1] = time.strftime("%x", time_tuple).lower()
|
||||||
|
date_time[2] = time.strftime("%X", time_tuple).lower()
|
||||||
|
replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
|
||||||
|
(self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
|
||||||
|
(self.a_month[3], '%b'), (self.am_pm[1], '%p'),
|
||||||
|
('1999', '%Y'), ('99', '%y'), ('22', '%H'),
|
||||||
|
('44', '%M'), ('55', '%S'), ('76', '%j'),
|
||||||
|
('17', '%d'), ('03', '%m'), ('3', '%m'),
|
||||||
|
# '3' needed for when no leading zero.
|
||||||
|
('2', '%w'), ('10', '%I')]
|
||||||
|
replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone
|
||||||
|
for tz in tz_values])
|
||||||
|
for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
|
||||||
|
current_format = date_time[offset]
|
||||||
|
for old, new in replacement_pairs:
|
||||||
|
# Must deal with possible lack of locale info
|
||||||
|
# manifesting itself as the empty string (e.g., Swedish's
|
||||||
|
# lack of AM/PM info) or a platform returning a tuple of empty
|
||||||
|
# strings (e.g., MacOS 9 having timezone as ('','')).
|
||||||
|
if old:
|
||||||
|
current_format = current_format.replace(old, new)
|
||||||
|
# If %W is used, then Sunday, 2005-01-03 will fall on week 0 since
|
||||||
|
# 2005-01-03 occurs before the first Monday of the year. Otherwise
|
||||||
|
# %U is used.
|
||||||
|
time_tuple = time.struct_time((1999,1,3,1,1,1,6,3,0))
|
||||||
|
if '00' in time.strftime(directive, time_tuple):
|
||||||
|
U_W = '%W'
|
||||||
|
else:
|
||||||
|
U_W = '%U'
|
||||||
|
date_time[offset] = current_format.replace('11', U_W)
|
||||||
|
self.LC_date_time = date_time[0]
|
||||||
|
self.LC_date = date_time[1]
|
||||||
|
self.LC_time = date_time[2]
|
||||||
|
|
||||||
|
def __calc_timezone(self):
|
||||||
|
# Set self.timezone by using time.tzname.
|
||||||
|
# Do not worry about possibility of time.tzname[0] == time.tzname[1]
|
||||||
|
# and time.daylight; handle that in strptime.
|
||||||
|
try:
|
||||||
|
time.tzset()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.tzname = time.tzname
|
||||||
|
self.daylight = time.daylight
|
||||||
|
no_saving = frozenset({"utc", "gmt", self.tzname[0].lower()})
|
||||||
|
if self.daylight:
|
||||||
|
has_saving = frozenset({self.tzname[1].lower()})
|
||||||
|
else:
|
||||||
|
has_saving = frozenset()
|
||||||
|
self.timezone = (no_saving, has_saving)
|
||||||
|
|
||||||
|
|
||||||
|
class TimeRE(dict):
|
||||||
|
"""Handle conversion from format directives to regexes."""
|
||||||
|
|
||||||
|
def __init__(self, locale_time=None):
|
||||||
|
"""Create keys/values.
|
||||||
|
|
||||||
|
Order of execution is important for dependency reasons.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if locale_time:
|
||||||
|
self.locale_time = locale_time
|
||||||
|
else:
|
||||||
|
self.locale_time = LocaleTime()
|
||||||
|
base = super()
|
||||||
|
base.__init__({
|
||||||
|
# The " [1-9]" part of the regex is to make %c from ANSI C work
|
||||||
|
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
|
||||||
|
'f': r"(?P<f>[0-9]{1,6})",
|
||||||
|
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
|
||||||
|
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
|
||||||
|
'G': r"(?P<G>\d\d\d\d)",
|
||||||
|
'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
|
||||||
|
'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
|
||||||
|
'M': r"(?P<M>[0-5]\d|\d)",
|
||||||
|
'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
|
||||||
|
'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
|
||||||
|
'w': r"(?P<w>[0-6])",
|
||||||
|
'u': r"(?P<u>[1-7])",
|
||||||
|
'V': r"(?P<V>5[0-3]|0[1-9]|[1-4]\d|\d)",
|
||||||
|
# W is set below by using 'U'
|
||||||
|
'y': r"(?P<y>\d\d)",
|
||||||
|
#XXX: Does 'Y' need to worry about having less or more than
|
||||||
|
# 4 digits?
|
||||||
|
'Y': r"(?P<Y>\d\d\d\d)",
|
||||||
|
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
|
||||||
|
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
|
||||||
|
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
|
||||||
|
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
|
||||||
|
'b': self.__seqToRE(self.locale_time.a_month[1:], 'b'),
|
||||||
|
'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
|
||||||
|
'Z': self.__seqToRE((tz for tz_names in self.locale_time.timezone
|
||||||
|
for tz in tz_names),
|
||||||
|
'Z'),
|
||||||
|
'%': '%'})
|
||||||
|
base.__setitem__('W', base.__getitem__('U').replace('U', 'W'))
|
||||||
|
base.__setitem__('c', self.pattern(self.locale_time.LC_date_time))
|
||||||
|
base.__setitem__('x', self.pattern(self.locale_time.LC_date))
|
||||||
|
base.__setitem__('X', self.pattern(self.locale_time.LC_time))
|
||||||
|
|
||||||
|
def __seqToRE(self, to_convert, directive):
|
||||||
|
"""Convert a list to a regex string for matching a directive.
|
||||||
|
|
||||||
|
Want possible matching values to be from longest to shortest. This
|
||||||
|
prevents the possibility of a match occurring for a value that also
|
||||||
|
a substring of a larger value that should have matched (e.g., 'abc'
|
||||||
|
matching when 'abcdef' should have been the match).
|
||||||
|
|
||||||
|
"""
|
||||||
|
to_convert = sorted(to_convert, key=len, reverse=True)
|
||||||
|
for value in to_convert:
|
||||||
|
if value != '':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
regex = '|'.join(re_escape(stuff) for stuff in to_convert)
|
||||||
|
regex = '(?P<%s>%s' % (directive, regex)
|
||||||
|
return '%s)' % regex
|
||||||
|
|
||||||
|
def pattern(self, format):
|
||||||
|
"""Return regex pattern for the format string.
|
||||||
|
|
||||||
|
Need to make sure that any characters that might be interpreted as
|
||||||
|
regex syntax are escaped.
|
||||||
|
|
||||||
|
"""
|
||||||
|
processed_format = ''
|
||||||
|
# The sub() call escapes all characters that might be misconstrued
|
||||||
|
# as regex syntax. Cannot use re.escape since we have to deal with
|
||||||
|
# format directives (%m, etc.).
|
||||||
|
regex_chars = re_compile(r"([\\.^$*+?\(\){}\[\]|])")
|
||||||
|
format = regex_chars.sub(r"\\\1", format)
|
||||||
|
whitespace_replacement = re_compile(r'\s+')
|
||||||
|
format = whitespace_replacement.sub(r'\\s+', format)
|
||||||
|
while '%' in format:
|
||||||
|
directive_index = format.index('%')+1
|
||||||
|
processed_format = "%s%s%s" % (processed_format,
|
||||||
|
format[:directive_index-1],
|
||||||
|
self[format[directive_index]])
|
||||||
|
format = format[directive_index+1:]
|
||||||
|
return "%s%s" % (processed_format, format)
|
||||||
|
|
||||||
|
def compile(self, format):
|
||||||
|
"""Return a compiled re object for the format string."""
|
||||||
|
return re_compile(self.pattern(format), IGNORECASE)
|
||||||
|
|
||||||
|
_cache_lock = _thread_allocate_lock()
|
||||||
|
# DO NOT modify _TimeRE_cache or _regex_cache without acquiring the cache lock
|
||||||
|
# first!
|
||||||
|
_TimeRE_cache = TimeRE()
|
||||||
|
_CACHE_MAX_SIZE = 5 # Max number of regexes stored in _regex_cache
|
||||||
|
_regex_cache = {}
|
||||||
|
|
||||||
|
def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
|
||||||
|
"""Calculate the Julian day based on the year, week of the year, and day of
|
||||||
|
the week, with week_start_day representing whether the week of the year
|
||||||
|
assumes the week starts on Sunday or Monday (6 or 0)."""
|
||||||
|
first_weekday = datetime_date(year, 1, 1).weekday()
|
||||||
|
# If we are dealing with the %U directive (week starts on Sunday), it's
|
||||||
|
# easier to just shift the view to Sunday being the first day of the
|
||||||
|
# week.
|
||||||
|
if not week_starts_Mon:
|
||||||
|
first_weekday = (first_weekday + 1) % 7
|
||||||
|
day_of_week = (day_of_week + 1) % 7
|
||||||
|
# Need to watch out for a week 0 (when the first day of the year is not
|
||||||
|
# the same as that specified by %U or %W).
|
||||||
|
week_0_length = (7 - first_weekday) % 7
|
||||||
|
if week_of_year == 0:
|
||||||
|
return 1 + day_of_week - first_weekday
|
||||||
|
else:
|
||||||
|
days_to_week = week_0_length + (7 * (week_of_year - 1))
|
||||||
|
return 1 + days_to_week + day_of_week
|
||||||
|
|
||||||
|
|
||||||
|
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||||
|
"""Return a 2-tuple consisting of a time struct and an int containing
|
||||||
|
the number of microseconds based on the input string and the
|
||||||
|
format string."""
|
||||||
|
|
||||||
|
for index, arg in enumerate([data_string, format]):
|
||||||
|
if not isinstance(arg, str):
|
||||||
|
msg = "strptime() argument {} must be str, not {}"
|
||||||
|
raise TypeError(msg.format(index, type(arg)))
|
||||||
|
|
||||||
|
global _TimeRE_cache, _regex_cache
|
||||||
|
with _cache_lock:
|
||||||
|
locale_time = _TimeRE_cache.locale_time
|
||||||
|
if (_getlang() != locale_time.lang or
|
||||||
|
time.tzname != locale_time.tzname or
|
||||||
|
time.daylight != locale_time.daylight):
|
||||||
|
_TimeRE_cache = TimeRE()
|
||||||
|
_regex_cache.clear()
|
||||||
|
locale_time = _TimeRE_cache.locale_time
|
||||||
|
if len(_regex_cache) > _CACHE_MAX_SIZE:
|
||||||
|
_regex_cache.clear()
|
||||||
|
format_regex = _regex_cache.get(format)
|
||||||
|
if not format_regex:
|
||||||
|
try:
|
||||||
|
format_regex = _TimeRE_cache.compile(format)
|
||||||
|
# KeyError raised when a bad format is found; can be specified as
|
||||||
|
# \\, in which case it was a stray % but with a space after it
|
||||||
|
except KeyError as err:
|
||||||
|
bad_directive = err.args[0]
|
||||||
|
if bad_directive == "\\":
|
||||||
|
bad_directive = "%"
|
||||||
|
del err
|
||||||
|
raise ValueError("'%s' is a bad directive in format '%s'" %
|
||||||
|
(bad_directive, format)) from None
|
||||||
|
# IndexError only occurs when the format string is "%"
|
||||||
|
except IndexError:
|
||||||
|
raise ValueError("stray %% in format '%s'" % format) from None
|
||||||
|
_regex_cache[format] = format_regex
|
||||||
|
found = format_regex.match(data_string)
|
||||||
|
if not found:
|
||||||
|
raise ValueError("time data %r does not match format %r" %
|
||||||
|
(data_string, format))
|
||||||
|
if len(data_string) != found.end():
|
||||||
|
raise ValueError("unconverted data remains: %s" %
|
||||||
|
data_string[found.end():])
|
||||||
|
|
||||||
|
iso_year = year = None
|
||||||
|
month = day = 1
|
||||||
|
hour = minute = second = fraction = 0
|
||||||
|
tz = -1
|
||||||
|
gmtoff = None
|
||||||
|
gmtoff_fraction = 0
|
||||||
|
iso_week = week_of_year = None
|
||||||
|
week_of_year_start = None
|
||||||
|
# weekday and julian defaulted to None so as to signal need to calculate
|
||||||
|
# values
|
||||||
|
weekday = julian = None
|
||||||
|
found_dict = found.groupdict()
|
||||||
|
for group_key in found_dict.keys():
|
||||||
|
# Directives not explicitly handled below:
|
||||||
|
# c, x, X
|
||||||
|
# handled by making out of other directives
|
||||||
|
# U, W
|
||||||
|
# worthless without day of the week
|
||||||
|
if group_key == 'y':
|
||||||
|
year = int(found_dict['y'])
|
||||||
|
# Open Group specification for strptime() states that a %y
|
||||||
|
#value in the range of [00, 68] is in the century 2000, while
|
||||||
|
#[69,99] is in the century 1900
|
||||||
|
if year <= 68:
|
||||||
|
year += 2000
|
||||||
|
else:
|
||||||
|
year += 1900
|
||||||
|
elif group_key == 'Y':
|
||||||
|
year = int(found_dict['Y'])
|
||||||
|
elif group_key == 'G':
|
||||||
|
iso_year = int(found_dict['G'])
|
||||||
|
elif group_key == 'm':
|
||||||
|
month = int(found_dict['m'])
|
||||||
|
elif group_key == 'B':
|
||||||
|
month = locale_time.f_month.index(found_dict['B'].lower())
|
||||||
|
elif group_key == 'b':
|
||||||
|
month = locale_time.a_month.index(found_dict['b'].lower())
|
||||||
|
elif group_key == 'd':
|
||||||
|
day = int(found_dict['d'])
|
||||||
|
elif group_key == 'H':
|
||||||
|
hour = int(found_dict['H'])
|
||||||
|
elif group_key == 'I':
|
||||||
|
hour = int(found_dict['I'])
|
||||||
|
ampm = found_dict.get('p', '').lower()
|
||||||
|
# If there was no AM/PM indicator, we'll treat this like AM
|
||||||
|
if ampm in ('', locale_time.am_pm[0]):
|
||||||
|
# We're in AM so the hour is correct unless we're
|
||||||
|
# looking at 12 midnight.
|
||||||
|
# 12 midnight == 12 AM == hour 0
|
||||||
|
if hour == 12:
|
||||||
|
hour = 0
|
||||||
|
elif ampm == locale_time.am_pm[1]:
|
||||||
|
# We're in PM so we need to add 12 to the hour unless
|
||||||
|
# we're looking at 12 noon.
|
||||||
|
# 12 noon == 12 PM == hour 12
|
||||||
|
if hour != 12:
|
||||||
|
hour += 12
|
||||||
|
elif group_key == 'M':
|
||||||
|
minute = int(found_dict['M'])
|
||||||
|
elif group_key == 'S':
|
||||||
|
second = int(found_dict['S'])
|
||||||
|
elif group_key == 'f':
|
||||||
|
s = found_dict['f']
|
||||||
|
# Pad to always return microseconds.
|
||||||
|
s += "0" * (6 - len(s))
|
||||||
|
fraction = int(s)
|
||||||
|
elif group_key == 'A':
|
||||||
|
weekday = locale_time.f_weekday.index(found_dict['A'].lower())
|
||||||
|
elif group_key == 'a':
|
||||||
|
weekday = locale_time.a_weekday.index(found_dict['a'].lower())
|
||||||
|
elif group_key == 'w':
|
||||||
|
weekday = int(found_dict['w'])
|
||||||
|
if weekday == 0:
|
||||||
|
weekday = 6
|
||||||
|
else:
|
||||||
|
weekday -= 1
|
||||||
|
elif group_key == 'u':
|
||||||
|
weekday = int(found_dict['u'])
|
||||||
|
weekday -= 1
|
||||||
|
elif group_key == 'j':
|
||||||
|
julian = int(found_dict['j'])
|
||||||
|
elif group_key in ('U', 'W'):
|
||||||
|
week_of_year = int(found_dict[group_key])
|
||||||
|
if group_key == 'U':
|
||||||
|
# U starts week on Sunday.
|
||||||
|
week_of_year_start = 6
|
||||||
|
else:
|
||||||
|
# W starts week on Monday.
|
||||||
|
week_of_year_start = 0
|
||||||
|
elif group_key == 'V':
|
||||||
|
iso_week = int(found_dict['V'])
|
||||||
|
elif group_key == 'z':
|
||||||
|
z = found_dict['z']
|
||||||
|
if z == 'Z':
|
||||||
|
gmtoff = 0
|
||||||
|
else:
|
||||||
|
if z[3] == ':':
|
||||||
|
z = z[:3] + z[4:]
|
||||||
|
if len(z) > 5:
|
||||||
|
if z[5] != ':':
|
||||||
|
msg = f"Inconsistent use of : in {found_dict['z']}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
z = z[:5] + z[6:]
|
||||||
|
hours = int(z[1:3])
|
||||||
|
minutes = int(z[3:5])
|
||||||
|
seconds = int(z[5:7] or 0)
|
||||||
|
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
|
||||||
|
gmtoff_remainder = z[8:]
|
||||||
|
# Pad to always return microseconds.
|
||||||
|
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
|
||||||
|
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
|
||||||
|
if z.startswith("-"):
|
||||||
|
gmtoff = -gmtoff
|
||||||
|
gmtoff_fraction = -gmtoff_fraction
|
||||||
|
elif group_key == 'Z':
|
||||||
|
# Since -1 is default value only need to worry about setting tz if
|
||||||
|
# it can be something other than -1.
|
||||||
|
found_zone = found_dict['Z'].lower()
|
||||||
|
for value, tz_values in enumerate(locale_time.timezone):
|
||||||
|
if found_zone in tz_values:
|
||||||
|
# Deal with bad locale setup where timezone names are the
|
||||||
|
# same and yet time.daylight is true; too ambiguous to
|
||||||
|
# be able to tell what timezone has daylight savings
|
||||||
|
if (time.tzname[0] == time.tzname[1] and
|
||||||
|
time.daylight and found_zone not in ("utc", "gmt")):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
tz = value
|
||||||
|
break
|
||||||
|
|
||||||
|
# Deal with the cases where ambiguities arise
|
||||||
|
# don't assume default values for ISO week/year
|
||||||
|
if iso_year is not None:
|
||||||
|
if julian is not None:
|
||||||
|
raise ValueError("Day of the year directive '%j' is not "
|
||||||
|
"compatible with ISO year directive '%G'. "
|
||||||
|
"Use '%Y' instead.")
|
||||||
|
elif iso_week is None or weekday is None:
|
||||||
|
raise ValueError("ISO year directive '%G' must be used with "
|
||||||
|
"the ISO week directive '%V' and a weekday "
|
||||||
|
"directive ('%A', '%a', '%w', or '%u').")
|
||||||
|
elif iso_week is not None:
|
||||||
|
if year is None or weekday is None:
|
||||||
|
raise ValueError("ISO week directive '%V' must be used with "
|
||||||
|
"the ISO year directive '%G' and a weekday "
|
||||||
|
"directive ('%A', '%a', '%w', or '%u').")
|
||||||
|
else:
|
||||||
|
raise ValueError("ISO week directive '%V' is incompatible with "
|
||||||
|
"the year directive '%Y'. Use the ISO year '%G' "
|
||||||
|
"instead.")
|
||||||
|
|
||||||
|
leap_year_fix = False
|
||||||
|
if year is None:
|
||||||
|
if month == 2 and day == 29:
|
||||||
|
year = 1904 # 1904 is first leap year of 20th century
|
||||||
|
leap_year_fix = True
|
||||||
|
else:
|
||||||
|
year = 1900
|
||||||
|
|
||||||
|
# If we know the week of the year and what day of that week, we can figure
|
||||||
|
# out the Julian day of the year.
|
||||||
|
if julian is None and weekday is not None:
|
||||||
|
if week_of_year is not None:
|
||||||
|
week_starts_Mon = True if week_of_year_start == 0 else False
|
||||||
|
julian = _calc_julian_from_U_or_W(year, week_of_year, weekday,
|
||||||
|
week_starts_Mon)
|
||||||
|
elif iso_year is not None and iso_week is not None:
|
||||||
|
datetime_result = datetime_date.fromisocalendar(iso_year, iso_week, weekday + 1)
|
||||||
|
year = datetime_result.year
|
||||||
|
month = datetime_result.month
|
||||||
|
day = datetime_result.day
|
||||||
|
if julian is not None and julian <= 0:
|
||||||
|
year -= 1
|
||||||
|
yday = 366 if calendar.isleap(year) else 365
|
||||||
|
julian += yday
|
||||||
|
|
||||||
|
if julian is None:
|
||||||
|
# Cannot pre-calculate datetime_date() since can change in Julian
|
||||||
|
# calculation and thus could have different value for the day of
|
||||||
|
# the week calculation.
|
||||||
|
# Need to add 1 to result since first day of the year is 1, not 0.
|
||||||
|
julian = datetime_date(year, month, day).toordinal() - \
|
||||||
|
datetime_date(year, 1, 1).toordinal() + 1
|
||||||
|
else: # Assume that if they bothered to include Julian day (or if it was
|
||||||
|
# calculated above with year/week/weekday) it will be accurate.
|
||||||
|
datetime_result = datetime_date.fromordinal(
|
||||||
|
(julian - 1) +
|
||||||
|
datetime_date(year, 1, 1).toordinal())
|
||||||
|
year = datetime_result.year
|
||||||
|
month = datetime_result.month
|
||||||
|
day = datetime_result.day
|
||||||
|
if weekday is None:
|
||||||
|
weekday = datetime_date(year, month, day).weekday()
|
||||||
|
# Add timezone info
|
||||||
|
tzname = found_dict.get("Z")
|
||||||
|
|
||||||
|
if leap_year_fix:
|
||||||
|
# the caller didn't supply a year but asked for Feb 29th. We couldn't
|
||||||
|
# use the default of 1900 for computations. We set it back to ensure
|
||||||
|
# that February 29th is smaller than March 1st.
|
||||||
|
year = 1900
|
||||||
|
|
||||||
|
return (year, month, day,
|
||||||
|
hour, minute, second,
|
||||||
|
weekday, julian, tz, tzname, gmtoff), fraction, gmtoff_fraction
|
||||||
|
|
||||||
|
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||||
|
"""Return a time struct based on the input string and the
|
||||||
|
format string."""
|
||||||
|
tt = _strptime(data_string, format)[0]
|
||||||
|
return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
|
||||||
|
|
||||||
|
def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
|
||||||
|
"""Return a class cls instance based on the input string and the
|
||||||
|
format string."""
|
||||||
|
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
|
||||||
|
tzname, gmtoff = tt[-2:]
|
||||||
|
args = tt[:6] + (fraction,)
|
||||||
|
if gmtoff is not None:
|
||||||
|
tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
|
||||||
|
if tzname:
|
||||||
|
tz = datetime_timezone(tzdelta, tzname)
|
||||||
|
else:
|
||||||
|
tz = datetime_timezone(tzdelta)
|
||||||
|
args += (tz,)
|
||||||
|
|
||||||
|
return cls(*args)
|
||||||
4
Lib/abc.py
vendored
4
Lib/abc.py
vendored
@@ -18,7 +18,7 @@ def abstractmethod(funcobj):
|
|||||||
|
|
||||||
class C(metaclass=ABCMeta):
|
class C(metaclass=ABCMeta):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def my_abstract_method(self, ...):
|
def my_abstract_method(self, arg1, arg2, argN):
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
funcobj.__isabstractmethod__ = True
|
funcobj.__isabstractmethod__ = True
|
||||||
@@ -106,7 +106,7 @@ else:
|
|||||||
implementations defined by the registering ABC be callable (not
|
implementations defined by the registering ABC be callable (not
|
||||||
even via super()).
|
even via super()).
|
||||||
"""
|
"""
|
||||||
def __new__(mcls, name, bases, namespace, **kwargs):
|
def __new__(mcls, name, bases, namespace, /, **kwargs):
|
||||||
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
|
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
|
||||||
_abc_init(cls)
|
_abc_init(cls)
|
||||||
return cls
|
return cls
|
||||||
|
|||||||
65
Lib/aifc.py
vendored
65
Lib/aifc.py
vendored
@@ -138,7 +138,11 @@ import struct
|
|||||||
import builtins
|
import builtins
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
__all__ = ["Error", "open", "openfp"]
|
__all__ = ["Error", "open"]
|
||||||
|
|
||||||
|
|
||||||
|
warnings._deprecated(__name__, remove=(3, 13))
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -251,7 +255,9 @@ def _write_float(f, x):
|
|||||||
_write_ulong(f, himant)
|
_write_ulong(f, himant)
|
||||||
_write_ulong(f, lomant)
|
_write_ulong(f, lomant)
|
||||||
|
|
||||||
from chunk import Chunk
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
|
from chunk import Chunk
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
_aifc_params = namedtuple('_aifc_params',
|
_aifc_params = namedtuple('_aifc_params',
|
||||||
@@ -447,21 +453,33 @@ class Aifc_read:
|
|||||||
#
|
#
|
||||||
|
|
||||||
def _alaw2lin(self, data):
|
def _alaw2lin(self, data):
|
||||||
import audioop
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
return audioop.alaw2lin(data, 2)
|
return audioop.alaw2lin(data, 2)
|
||||||
|
|
||||||
def _ulaw2lin(self, data):
|
def _ulaw2lin(self, data):
|
||||||
import audioop
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
return audioop.ulaw2lin(data, 2)
|
return audioop.ulaw2lin(data, 2)
|
||||||
|
|
||||||
def _adpcm2lin(self, data):
|
def _adpcm2lin(self, data):
|
||||||
import audioop
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
if not hasattr(self, '_adpcmstate'):
|
if not hasattr(self, '_adpcmstate'):
|
||||||
# first time
|
# first time
|
||||||
self._adpcmstate = None
|
self._adpcmstate = None
|
||||||
data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
|
data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _sowt2lin(self, data):
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
|
return audioop.byteswap(data, 2)
|
||||||
|
|
||||||
def _read_comm_chunk(self, chunk):
|
def _read_comm_chunk(self, chunk):
|
||||||
self._nchannels = _read_short(chunk)
|
self._nchannels = _read_short(chunk)
|
||||||
self._nframes = _read_long(chunk)
|
self._nframes = _read_long(chunk)
|
||||||
@@ -497,6 +515,8 @@ class Aifc_read:
|
|||||||
self._convert = self._ulaw2lin
|
self._convert = self._ulaw2lin
|
||||||
elif self._comptype in (b'alaw', b'ALAW'):
|
elif self._comptype in (b'alaw', b'ALAW'):
|
||||||
self._convert = self._alaw2lin
|
self._convert = self._alaw2lin
|
||||||
|
elif self._comptype in (b'sowt', b'SOWT'):
|
||||||
|
self._convert = self._sowt2lin
|
||||||
else:
|
else:
|
||||||
raise Error('unsupported compression type')
|
raise Error('unsupported compression type')
|
||||||
self._sampwidth = 2
|
self._sampwidth = 2
|
||||||
@@ -659,7 +679,7 @@ class Aifc_write:
|
|||||||
if self._nframeswritten:
|
if self._nframeswritten:
|
||||||
raise Error('cannot change parameters after starting to write')
|
raise Error('cannot change parameters after starting to write')
|
||||||
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
||||||
b'alaw', b'ALAW', b'G722'):
|
b'alaw', b'ALAW', b'G722', b'sowt', b'SOWT'):
|
||||||
raise Error('unsupported compression type')
|
raise Error('unsupported compression type')
|
||||||
self._comptype = comptype
|
self._comptype = comptype
|
||||||
self._compname = compname
|
self._compname = compname
|
||||||
@@ -680,7 +700,7 @@ class Aifc_write:
|
|||||||
if self._nframeswritten:
|
if self._nframeswritten:
|
||||||
raise Error('cannot change parameters after starting to write')
|
raise Error('cannot change parameters after starting to write')
|
||||||
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
||||||
b'alaw', b'ALAW', b'G722'):
|
b'alaw', b'ALAW', b'G722', b'sowt', b'SOWT'):
|
||||||
raise Error('unsupported compression type')
|
raise Error('unsupported compression type')
|
||||||
self.setnchannels(nchannels)
|
self.setnchannels(nchannels)
|
||||||
self.setsampwidth(sampwidth)
|
self.setsampwidth(sampwidth)
|
||||||
@@ -764,28 +784,43 @@ class Aifc_write:
|
|||||||
#
|
#
|
||||||
|
|
||||||
def _lin2alaw(self, data):
|
def _lin2alaw(self, data):
|
||||||
import audioop
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
return audioop.lin2alaw(data, 2)
|
return audioop.lin2alaw(data, 2)
|
||||||
|
|
||||||
def _lin2ulaw(self, data):
|
def _lin2ulaw(self, data):
|
||||||
import audioop
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
return audioop.lin2ulaw(data, 2)
|
return audioop.lin2ulaw(data, 2)
|
||||||
|
|
||||||
def _lin2adpcm(self, data):
|
def _lin2adpcm(self, data):
|
||||||
import audioop
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
if not hasattr(self, '_adpcmstate'):
|
if not hasattr(self, '_adpcmstate'):
|
||||||
self._adpcmstate = None
|
self._adpcmstate = None
|
||||||
data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
|
data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _lin2sowt(self, data):
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', category=DeprecationWarning)
|
||||||
|
import audioop
|
||||||
|
return audioop.byteswap(data, 2)
|
||||||
|
|
||||||
def _ensure_header_written(self, datasize):
|
def _ensure_header_written(self, datasize):
|
||||||
if not self._nframeswritten:
|
if not self._nframeswritten:
|
||||||
if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
|
if self._comptype in (b'ULAW', b'ulaw',
|
||||||
|
b'ALAW', b'alaw', b'G722',
|
||||||
|
b'sowt', b'SOWT'):
|
||||||
if not self._sampwidth:
|
if not self._sampwidth:
|
||||||
self._sampwidth = 2
|
self._sampwidth = 2
|
||||||
if self._sampwidth != 2:
|
if self._sampwidth != 2:
|
||||||
raise Error('sample width must be 2 when compressing '
|
raise Error('sample width must be 2 when compressing '
|
||||||
'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
|
'with ulaw/ULAW, alaw/ALAW, sowt/SOWT '
|
||||||
|
'or G7.22 (ADPCM)')
|
||||||
if not self._nchannels:
|
if not self._nchannels:
|
||||||
raise Error('# channels not specified')
|
raise Error('# channels not specified')
|
||||||
if not self._sampwidth:
|
if not self._sampwidth:
|
||||||
@@ -801,6 +836,8 @@ class Aifc_write:
|
|||||||
self._convert = self._lin2ulaw
|
self._convert = self._lin2ulaw
|
||||||
elif self._comptype in (b'alaw', b'ALAW'):
|
elif self._comptype in (b'alaw', b'ALAW'):
|
||||||
self._convert = self._lin2alaw
|
self._convert = self._lin2alaw
|
||||||
|
elif self._comptype in (b'sowt', b'SOWT'):
|
||||||
|
self._convert = self._lin2sowt
|
||||||
|
|
||||||
def _write_header(self, initlength):
|
def _write_header(self, initlength):
|
||||||
if self._aifc and self._comptype != b'NONE':
|
if self._aifc and self._comptype != b'NONE':
|
||||||
@@ -920,10 +957,6 @@ def open(f, mode=None):
|
|||||||
else:
|
else:
|
||||||
raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
|
raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
|
||||||
|
|
||||||
def openfp(f, mode=None):
|
|
||||||
warnings.warn("aifc.openfp is deprecated since Python 3.7. "
|
|
||||||
"Use aifc.open instead.", DeprecationWarning, stacklevel=2)
|
|
||||||
return open(f, mode=mode)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
4
Lib/antigravity.py
vendored
4
Lib/antigravity.py
vendored
@@ -11,7 +11,7 @@ def geohash(latitude, longitude, datedow):
|
|||||||
37.857713 -122.544543
|
37.857713 -122.544543
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# http://xkcd.com/426/
|
# https://xkcd.com/426/
|
||||||
h = hashlib.md5(datedow).hexdigest()
|
h = hashlib.md5(datedow, usedforsecurity=False).hexdigest()
|
||||||
p, q = [('%f' % float.fromhex('0.' + x)) for x in (h[:16], h[16:32])]
|
p, q = [('%f' % float.fromhex('0.' + x)) for x in (h[:16], h[16:32])]
|
||||||
print('%d%s %d%s' % (latitude, p[1:], longitude, q[1:]))
|
print('%d%s %d%s' % (latitude, p[1:], longitude, q[1:]))
|
||||||
|
|||||||
130
Lib/argparse.py
vendored
130
Lib/argparse.py
vendored
@@ -89,6 +89,8 @@ import os as _os
|
|||||||
import re as _re
|
import re as _re
|
||||||
import sys as _sys
|
import sys as _sys
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
from gettext import gettext as _, ngettext
|
from gettext import gettext as _, ngettext
|
||||||
|
|
||||||
SUPPRESS = '==SUPPRESS=='
|
SUPPRESS = '==SUPPRESS=='
|
||||||
@@ -151,6 +153,7 @@ def _copy_items(items):
|
|||||||
# Formatting Help
|
# Formatting Help
|
||||||
# ===============
|
# ===============
|
||||||
|
|
||||||
|
|
||||||
class HelpFormatter(object):
|
class HelpFormatter(object):
|
||||||
"""Formatter for generating usage messages and argument help strings.
|
"""Formatter for generating usage messages and argument help strings.
|
||||||
|
|
||||||
@@ -342,21 +345,22 @@ class HelpFormatter(object):
|
|||||||
def get_lines(parts, indent, prefix=None):
|
def get_lines(parts, indent, prefix=None):
|
||||||
lines = []
|
lines = []
|
||||||
line = []
|
line = []
|
||||||
|
indent_length = len(indent)
|
||||||
if prefix is not None:
|
if prefix is not None:
|
||||||
line_len = len(prefix) - 1
|
line_len = len(prefix) - 1
|
||||||
else:
|
else:
|
||||||
line_len = len(indent) - 1
|
line_len = indent_length - 1
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if line_len + 1 + len(part) > text_width and line:
|
if line_len + 1 + len(part) > text_width and line:
|
||||||
lines.append(indent + ' '.join(line))
|
lines.append(indent + ' '.join(line))
|
||||||
line = []
|
line = []
|
||||||
line_len = len(indent) - 1
|
line_len = indent_length - 1
|
||||||
line.append(part)
|
line.append(part)
|
||||||
line_len += len(part) + 1
|
line_len += len(part) + 1
|
||||||
if line:
|
if line:
|
||||||
lines.append(indent + ' '.join(line))
|
lines.append(indent + ' '.join(line))
|
||||||
if prefix is not None:
|
if prefix is not None:
|
||||||
lines[0] = lines[0][len(indent):]
|
lines[0] = lines[0][indent_length:]
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
# if prog is short, follow it with optionals or positionals
|
# if prog is short, follow it with optionals or positionals
|
||||||
@@ -400,10 +404,18 @@ class HelpFormatter(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
end = start + len(group._group_actions)
|
group_action_count = len(group._group_actions)
|
||||||
|
end = start + group_action_count
|
||||||
if actions[start:end] == group._group_actions:
|
if actions[start:end] == group._group_actions:
|
||||||
|
|
||||||
|
suppressed_actions_count = 0
|
||||||
for action in group._group_actions:
|
for action in group._group_actions:
|
||||||
group_actions.add(action)
|
group_actions.add(action)
|
||||||
|
if action.help is SUPPRESS:
|
||||||
|
suppressed_actions_count += 1
|
||||||
|
|
||||||
|
exposed_actions_count = group_action_count - suppressed_actions_count
|
||||||
|
|
||||||
if not group.required:
|
if not group.required:
|
||||||
if start in inserts:
|
if start in inserts:
|
||||||
inserts[start] += ' ['
|
inserts[start] += ' ['
|
||||||
@@ -413,7 +425,7 @@ class HelpFormatter(object):
|
|||||||
inserts[end] += ']'
|
inserts[end] += ']'
|
||||||
else:
|
else:
|
||||||
inserts[end] = ']'
|
inserts[end] = ']'
|
||||||
else:
|
elif exposed_actions_count > 1:
|
||||||
if start in inserts:
|
if start in inserts:
|
||||||
inserts[start] += ' ('
|
inserts[start] += ' ('
|
||||||
else:
|
else:
|
||||||
@@ -487,7 +499,6 @@ class HelpFormatter(object):
|
|||||||
text = _re.sub(r'(%s) ' % open, r'\1', text)
|
text = _re.sub(r'(%s) ' % open, r'\1', text)
|
||||||
text = _re.sub(r' (%s)' % close, r'\1', text)
|
text = _re.sub(r' (%s)' % close, r'\1', text)
|
||||||
text = _re.sub(r'%s *%s' % (open, close), r'', text)
|
text = _re.sub(r'%s *%s' % (open, close), r'', text)
|
||||||
text = _re.sub(r'\(([^|]*)\)', r'\1', text)
|
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
|
|
||||||
# return the text
|
# return the text
|
||||||
@@ -693,8 +704,19 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def _get_help_string(self, action):
|
def _get_help_string(self, action):
|
||||||
|
"""
|
||||||
|
Add the default value to the option help message.
|
||||||
|
|
||||||
|
ArgumentDefaultsHelpFormatter and BooleanOptionalAction when it isn't
|
||||||
|
already present. This code will do that, detecting cornercases to
|
||||||
|
prevent duplicates or cases where it wouldn't make sense to the end
|
||||||
|
user.
|
||||||
|
"""
|
||||||
help = action.help
|
help = action.help
|
||||||
if '%(default)' not in action.help:
|
if help is None:
|
||||||
|
help = ''
|
||||||
|
|
||||||
|
if '%(default)' not in help:
|
||||||
if action.default is not SUPPRESS:
|
if action.default is not SUPPRESS:
|
||||||
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
|
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
|
||||||
if action.option_strings or action.nargs in defaulting_nargs:
|
if action.option_strings or action.nargs in defaulting_nargs:
|
||||||
@@ -702,6 +724,7 @@ class ArgumentDefaultsHelpFormatter(HelpFormatter):
|
|||||||
return help
|
return help
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MetavarTypeHelpFormatter(HelpFormatter):
|
class MetavarTypeHelpFormatter(HelpFormatter):
|
||||||
"""Help message formatter which uses the argument 'type' as the default
|
"""Help message formatter which uses the argument 'type' as the default
|
||||||
metavar value (instead of the argument 'dest')
|
metavar value (instead of the argument 'dest')
|
||||||
@@ -717,7 +740,6 @@ class MetavarTypeHelpFormatter(HelpFormatter):
|
|||||||
return action.type.__name__
|
return action.type.__name__
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# Options and Arguments
|
# Options and Arguments
|
||||||
# =====================
|
# =====================
|
||||||
@@ -752,7 +774,7 @@ class ArgumentError(Exception):
|
|||||||
if self.argument_name is None:
|
if self.argument_name is None:
|
||||||
format = '%(message)s'
|
format = '%(message)s'
|
||||||
else:
|
else:
|
||||||
format = 'argument %(argument_name)s: %(message)s'
|
format = _('argument %(argument_name)s: %(message)s')
|
||||||
return format % dict(message=self.message,
|
return format % dict(message=self.message,
|
||||||
argument_name=self.argument_name)
|
argument_name=self.argument_name)
|
||||||
|
|
||||||
@@ -860,16 +882,20 @@ class Action(_AttributeHolder):
|
|||||||
def __call__(self, parser, namespace, values, option_string=None):
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
raise NotImplementedError(_('.__call__() not defined'))
|
raise NotImplementedError(_('.__call__() not defined'))
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: remove together with `BooleanOptionalAction` deprecated arguments.
|
||||||
|
_deprecated_default = object()
|
||||||
|
|
||||||
class BooleanOptionalAction(Action):
|
class BooleanOptionalAction(Action):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
option_strings,
|
option_strings,
|
||||||
dest,
|
dest,
|
||||||
default=None,
|
default=None,
|
||||||
type=None,
|
type=_deprecated_default,
|
||||||
choices=None,
|
choices=_deprecated_default,
|
||||||
required=False,
|
required=False,
|
||||||
help=None,
|
help=None,
|
||||||
metavar=None):
|
metavar=_deprecated_default):
|
||||||
|
|
||||||
_option_strings = []
|
_option_strings = []
|
||||||
for option_string in option_strings:
|
for option_string in option_strings:
|
||||||
@@ -879,8 +905,23 @@ class BooleanOptionalAction(Action):
|
|||||||
option_string = '--no-' + option_string[2:]
|
option_string = '--no-' + option_string[2:]
|
||||||
_option_strings.append(option_string)
|
_option_strings.append(option_string)
|
||||||
|
|
||||||
if help is not None and default is not None and default is not SUPPRESS:
|
# We need `_deprecated` special value to ban explicit arguments that
|
||||||
help += " (default: %(default)s)"
|
# match default value. Like:
|
||||||
|
# parser.add_argument('-f', action=BooleanOptionalAction, type=int)
|
||||||
|
for field_name in ('type', 'choices', 'metavar'):
|
||||||
|
if locals()[field_name] is not _deprecated_default:
|
||||||
|
warnings._deprecated(
|
||||||
|
field_name,
|
||||||
|
"{name!r} is deprecated as of Python 3.12 and will be "
|
||||||
|
"removed in Python {remove}.",
|
||||||
|
remove=(3, 14))
|
||||||
|
|
||||||
|
if type is _deprecated_default:
|
||||||
|
type = None
|
||||||
|
if choices is _deprecated_default:
|
||||||
|
choices = None
|
||||||
|
if metavar is _deprecated_default:
|
||||||
|
metavar = None
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
option_strings=_option_strings,
|
option_strings=_option_strings,
|
||||||
@@ -893,6 +934,7 @@ class BooleanOptionalAction(Action):
|
|||||||
help=help,
|
help=help,
|
||||||
metavar=metavar)
|
metavar=metavar)
|
||||||
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
if option_string in self.option_strings:
|
if option_string in self.option_strings:
|
||||||
setattr(namespace, self.dest, not option_string.startswith('--no-'))
|
setattr(namespace, self.dest, not option_string.startswith('--no-'))
|
||||||
@@ -941,7 +983,7 @@ class _StoreConstAction(Action):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
option_strings,
|
option_strings,
|
||||||
dest,
|
dest,
|
||||||
const,
|
const=None,
|
||||||
default=None,
|
default=None,
|
||||||
required=False,
|
required=False,
|
||||||
help=None,
|
help=None,
|
||||||
@@ -1036,7 +1078,7 @@ class _AppendConstAction(Action):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
option_strings,
|
option_strings,
|
||||||
dest,
|
dest,
|
||||||
const,
|
const=None,
|
||||||
default=None,
|
default=None,
|
||||||
required=False,
|
required=False,
|
||||||
help=None,
|
help=None,
|
||||||
@@ -1168,6 +1210,13 @@ class _SubParsersAction(Action):
|
|||||||
|
|
||||||
aliases = kwargs.pop('aliases', ())
|
aliases = kwargs.pop('aliases', ())
|
||||||
|
|
||||||
|
if name in self._name_parser_map:
|
||||||
|
raise ArgumentError(self, _('conflicting subparser: %s') % name)
|
||||||
|
for alias in aliases:
|
||||||
|
if alias in self._name_parser_map:
|
||||||
|
raise ArgumentError(
|
||||||
|
self, _('conflicting subparser alias: %s') % alias)
|
||||||
|
|
||||||
# create a pseudo-action to hold the choice help
|
# create a pseudo-action to hold the choice help
|
||||||
if 'help' in kwargs:
|
if 'help' in kwargs:
|
||||||
help = kwargs.pop('help')
|
help = kwargs.pop('help')
|
||||||
@@ -1648,6 +1697,14 @@ class _ArgumentGroup(_ActionsContainer):
|
|||||||
super(_ArgumentGroup, self)._remove_action(action)
|
super(_ArgumentGroup, self)._remove_action(action)
|
||||||
self._group_actions.remove(action)
|
self._group_actions.remove(action)
|
||||||
|
|
||||||
|
def add_argument_group(self, *args, **kwargs):
|
||||||
|
warnings.warn(
|
||||||
|
"Nesting argument groups is deprecated.",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
return super().add_argument_group(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class _MutuallyExclusiveGroup(_ArgumentGroup):
|
class _MutuallyExclusiveGroup(_ArgumentGroup):
|
||||||
|
|
||||||
@@ -1668,6 +1725,14 @@ class _MutuallyExclusiveGroup(_ArgumentGroup):
|
|||||||
self._container._remove_action(action)
|
self._container._remove_action(action)
|
||||||
self._group_actions.remove(action)
|
self._group_actions.remove(action)
|
||||||
|
|
||||||
|
def add_mutually_exclusive_group(self, *args, **kwargs):
|
||||||
|
warnings.warn(
|
||||||
|
"Nesting mutually exclusive groups is deprecated.",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
|
return super().add_mutually_exclusive_group(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||||
"""Object for parsing command line strings into Python objects.
|
"""Object for parsing command line strings into Python objects.
|
||||||
@@ -1857,8 +1922,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
if self.exit_on_error:
|
if self.exit_on_error:
|
||||||
try:
|
try:
|
||||||
namespace, args = self._parse_known_args(args, namespace)
|
namespace, args = self._parse_known_args(args, namespace)
|
||||||
except ArgumentError:
|
except ArgumentError as err:
|
||||||
err = _sys.exc_info()[1]
|
|
||||||
self.error(str(err))
|
self.error(str(err))
|
||||||
else:
|
else:
|
||||||
namespace, args = self._parse_known_args(args, namespace)
|
namespace, args = self._parse_known_args(args, namespace)
|
||||||
@@ -1962,7 +2026,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
# arguments, try to parse more single-dash options out
|
# arguments, try to parse more single-dash options out
|
||||||
# of the tail of the option string
|
# of the tail of the option string
|
||||||
chars = self.prefix_chars
|
chars = self.prefix_chars
|
||||||
if arg_count == 0 and option_string[1] not in chars:
|
if (
|
||||||
|
arg_count == 0
|
||||||
|
and option_string[1] not in chars
|
||||||
|
and explicit_arg != ''
|
||||||
|
):
|
||||||
action_tuples.append((action, [], option_string))
|
action_tuples.append((action, [], option_string))
|
||||||
char = option_string[0]
|
char = option_string[0]
|
||||||
option_string = char + explicit_arg[0]
|
option_string = char + explicit_arg[0]
|
||||||
@@ -2126,15 +2194,16 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
# replace arguments referencing files with the file content
|
# replace arguments referencing files with the file content
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
with open(arg_string[1:]) as args_file:
|
with open(arg_string[1:],
|
||||||
|
encoding=_sys.getfilesystemencoding(),
|
||||||
|
errors=_sys.getfilesystemencodeerrors()) as args_file:
|
||||||
arg_strings = []
|
arg_strings = []
|
||||||
for arg_line in args_file.read().splitlines():
|
for arg_line in args_file.read().splitlines():
|
||||||
for arg in self.convert_arg_line_to_args(arg_line):
|
for arg in self.convert_arg_line_to_args(arg_line):
|
||||||
arg_strings.append(arg)
|
arg_strings.append(arg)
|
||||||
arg_strings = self._read_args_from_files(arg_strings)
|
arg_strings = self._read_args_from_files(arg_strings)
|
||||||
new_arg_strings.extend(arg_strings)
|
new_arg_strings.extend(arg_strings)
|
||||||
except OSError:
|
except OSError as err:
|
||||||
err = _sys.exc_info()[1]
|
|
||||||
self.error(str(err))
|
self.error(str(err))
|
||||||
|
|
||||||
# return the modified argument list
|
# return the modified argument list
|
||||||
@@ -2441,9 +2510,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
not action.option_strings):
|
not action.option_strings):
|
||||||
if action.default is not None:
|
if action.default is not None:
|
||||||
value = action.default
|
value = action.default
|
||||||
|
self._check_value(action, value)
|
||||||
else:
|
else:
|
||||||
|
# since arg_strings is always [] at this point
|
||||||
|
# there is no need to use self._check_value(action, value)
|
||||||
value = arg_strings
|
value = arg_strings
|
||||||
self._check_value(action, value)
|
|
||||||
|
|
||||||
# single argument or optional argument produces a single value
|
# single argument or optional argument produces a single value
|
||||||
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
|
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
|
||||||
@@ -2484,9 +2555,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
result = type_func(arg_string)
|
result = type_func(arg_string)
|
||||||
|
|
||||||
# ArgumentTypeErrors indicate errors
|
# ArgumentTypeErrors indicate errors
|
||||||
except ArgumentTypeError:
|
except ArgumentTypeError as err:
|
||||||
name = getattr(action.type, '__name__', repr(action.type))
|
msg = str(err)
|
||||||
msg = str(_sys.exc_info()[1])
|
|
||||||
raise ArgumentError(action, msg)
|
raise ArgumentError(action, msg)
|
||||||
|
|
||||||
# TypeErrors or ValueErrors also indicate errors
|
# TypeErrors or ValueErrors also indicate errors
|
||||||
@@ -2557,9 +2627,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
|
|
||||||
def _print_message(self, message, file=None):
|
def _print_message(self, message, file=None):
|
||||||
if message:
|
if message:
|
||||||
if file is None:
|
file = file or _sys.stderr
|
||||||
file = _sys.stderr
|
try:
|
||||||
file.write(message)
|
file.write(message)
|
||||||
|
except (AttributeError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
# ===============
|
# ===============
|
||||||
# Exiting methods
|
# Exiting methods
|
||||||
|
|||||||
362
Lib/ast.py
vendored
362
Lib/ast.py
vendored
@@ -25,9 +25,10 @@
|
|||||||
:license: Python License.
|
:license: Python License.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
import re
|
||||||
from _ast import *
|
from _ast import *
|
||||||
from contextlib import contextmanager, nullcontext
|
from contextlib import contextmanager, nullcontext
|
||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto, _simple_enum
|
||||||
|
|
||||||
|
|
||||||
def parse(source, filename='<unknown>', mode='exec', *,
|
def parse(source, filename='<unknown>', mode='exec', *,
|
||||||
@@ -40,12 +41,13 @@ def parse(source, filename='<unknown>', mode='exec', *,
|
|||||||
flags = PyCF_ONLY_AST
|
flags = PyCF_ONLY_AST
|
||||||
if type_comments:
|
if type_comments:
|
||||||
flags |= PyCF_TYPE_COMMENTS
|
flags |= PyCF_TYPE_COMMENTS
|
||||||
if isinstance(feature_version, tuple):
|
if feature_version is None:
|
||||||
major, minor = feature_version # Should be a 2-tuple.
|
|
||||||
assert major == 3
|
|
||||||
feature_version = minor
|
|
||||||
elif feature_version is None:
|
|
||||||
feature_version = -1
|
feature_version = -1
|
||||||
|
elif isinstance(feature_version, tuple):
|
||||||
|
major, minor = feature_version # Should be a 2-tuple.
|
||||||
|
if major != 3:
|
||||||
|
raise ValueError(f"Unsupported major version: {major}")
|
||||||
|
feature_version = minor
|
||||||
# Else it should be an int giving the minor version for 3.x.
|
# Else it should be an int giving the minor version for 3.x.
|
||||||
return compile(source, filename, mode, flags,
|
return compile(source, filename, mode, flags,
|
||||||
_feature_version=feature_version)
|
_feature_version=feature_version)
|
||||||
@@ -53,10 +55,12 @@ def parse(source, filename='<unknown>', mode='exec', *,
|
|||||||
|
|
||||||
def literal_eval(node_or_string):
|
def literal_eval(node_or_string):
|
||||||
"""
|
"""
|
||||||
Safely evaluate an expression node or a string containing a Python
|
Evaluate an expression node or a string containing only a Python
|
||||||
expression. The string or node provided may only consist of the following
|
expression. The string or node provided may only consist of the following
|
||||||
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
|
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
|
||||||
sets, booleans, and None.
|
sets, booleans, and None.
|
||||||
|
|
||||||
|
Caution: A complex expression can overflow the C stack and cause a crash.
|
||||||
"""
|
"""
|
||||||
if isinstance(node_or_string, str):
|
if isinstance(node_or_string, str):
|
||||||
node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval')
|
node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval')
|
||||||
@@ -234,6 +238,12 @@ def increment_lineno(node, n=1):
|
|||||||
location in a file.
|
location in a file.
|
||||||
"""
|
"""
|
||||||
for child in walk(node):
|
for child in walk(node):
|
||||||
|
# TypeIgnore is a special case where lineno is not an attribute
|
||||||
|
# but rather a field of the node itself.
|
||||||
|
if isinstance(child, TypeIgnore):
|
||||||
|
child.lineno = getattr(child, 'lineno', 0) + n
|
||||||
|
continue
|
||||||
|
|
||||||
if 'lineno' in child._attributes:
|
if 'lineno' in child._attributes:
|
||||||
child.lineno = getattr(child, 'lineno', 0) + n
|
child.lineno = getattr(child, 'lineno', 0) + n
|
||||||
if (
|
if (
|
||||||
@@ -284,9 +294,7 @@ def get_docstring(node, clean=True):
|
|||||||
if not(node.body and isinstance(node.body[0], Expr)):
|
if not(node.body and isinstance(node.body[0], Expr)):
|
||||||
return None
|
return None
|
||||||
node = node.body[0].value
|
node = node.body[0].value
|
||||||
if isinstance(node, Str):
|
if isinstance(node, Constant) and isinstance(node.value, str):
|
||||||
text = node.s
|
|
||||||
elif isinstance(node, Constant) and isinstance(node.value, str):
|
|
||||||
text = node.value
|
text = node.value
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -296,28 +304,17 @@ def get_docstring(node, clean=True):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def _splitlines_no_ff(source):
|
_line_pattern = re.compile(r"(.*?(?:\r\n|\n|\r|$))")
|
||||||
|
def _splitlines_no_ff(source, maxlines=None):
|
||||||
"""Split a string into lines ignoring form feed and other chars.
|
"""Split a string into lines ignoring form feed and other chars.
|
||||||
|
|
||||||
This mimics how the Python parser splits source code.
|
This mimics how the Python parser splits source code.
|
||||||
"""
|
"""
|
||||||
idx = 0
|
|
||||||
lines = []
|
lines = []
|
||||||
next_line = ''
|
for lineno, match in enumerate(_line_pattern.finditer(source), 1):
|
||||||
while idx < len(source):
|
if maxlines is not None and lineno > maxlines:
|
||||||
c = source[idx]
|
break
|
||||||
next_line += c
|
lines.append(match[0])
|
||||||
idx += 1
|
|
||||||
# Keep \r\n together
|
|
||||||
if c == '\r' and idx < len(source) and source[idx] == '\n':
|
|
||||||
next_line += '\n'
|
|
||||||
idx += 1
|
|
||||||
if c in '\r\n':
|
|
||||||
lines.append(next_line)
|
|
||||||
next_line = ''
|
|
||||||
|
|
||||||
if next_line:
|
|
||||||
lines.append(next_line)
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
@@ -351,7 +348,7 @@ def get_source_segment(source, node, *, padded=False):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
lines = _splitlines_no_ff(source)
|
lines = _splitlines_no_ff(source, maxlines=end_lineno+1)
|
||||||
if end_lineno == lineno:
|
if end_lineno == lineno:
|
||||||
return lines[lineno].encode()[col_offset:end_col_offset].decode()
|
return lines[lineno].encode()[col_offset:end_col_offset].decode()
|
||||||
|
|
||||||
@@ -500,20 +497,52 @@ class NodeTransformer(NodeVisitor):
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
_DEPRECATED_VALUE_ALIAS_MESSAGE = (
|
||||||
|
"{name} is deprecated and will be removed in Python {remove}; use value instead"
|
||||||
|
)
|
||||||
|
_DEPRECATED_CLASS_MESSAGE = (
|
||||||
|
"{name} is deprecated and will be removed in Python {remove}; "
|
||||||
|
"use ast.Constant instead"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# If the ast module is loaded more than once, only add deprecated methods once
|
# If the ast module is loaded more than once, only add deprecated methods once
|
||||||
if not hasattr(Constant, 'n'):
|
if not hasattr(Constant, 'n'):
|
||||||
# The following code is for backward compatibility.
|
# The following code is for backward compatibility.
|
||||||
# It will be removed in future.
|
# It will be removed in future.
|
||||||
|
|
||||||
def _getter(self):
|
def _n_getter(self):
|
||||||
"""Deprecated. Use value instead."""
|
"""Deprecated. Use value instead."""
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def _setter(self, value):
|
def _n_setter(self, value):
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
Constant.n = property(_getter, _setter)
|
def _s_getter(self):
|
||||||
Constant.s = property(_getter, _setter)
|
"""Deprecated. Use value instead."""
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def _s_setter(self, value):
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
Constant.n = property(_n_getter, _n_setter)
|
||||||
|
Constant.s = property(_s_getter, _s_setter)
|
||||||
|
|
||||||
class _ABC(type):
|
class _ABC(type):
|
||||||
|
|
||||||
@@ -521,6 +550,13 @@ class _ABC(type):
|
|||||||
cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead"""
|
cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead"""
|
||||||
|
|
||||||
def __instancecheck__(cls, inst):
|
def __instancecheck__(cls, inst):
|
||||||
|
if cls in _const_types:
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
f"ast.{cls.__qualname__}",
|
||||||
|
message=_DEPRECATED_CLASS_MESSAGE,
|
||||||
|
remove=(3, 14)
|
||||||
|
)
|
||||||
if not isinstance(inst, Constant):
|
if not isinstance(inst, Constant):
|
||||||
return False
|
return False
|
||||||
if cls in _const_types:
|
if cls in _const_types:
|
||||||
@@ -544,6 +580,10 @@ def _new(cls, *args, **kwargs):
|
|||||||
if pos < len(args):
|
if pos < len(args):
|
||||||
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
|
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
|
||||||
if cls in _const_types:
|
if cls in _const_types:
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
return Constant(*args, **kwargs)
|
return Constant(*args, **kwargs)
|
||||||
return Constant.__new__(cls, *args, **kwargs)
|
return Constant.__new__(cls, *args, **kwargs)
|
||||||
|
|
||||||
@@ -566,10 +606,19 @@ class Ellipsis(Constant, metaclass=_ABC):
|
|||||||
_fields = ()
|
_fields = ()
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls is Ellipsis:
|
if cls is _ast_Ellipsis:
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
return Constant(..., *args, **kwargs)
|
return Constant(..., *args, **kwargs)
|
||||||
return Constant.__new__(cls, *args, **kwargs)
|
return Constant.__new__(cls, *args, **kwargs)
|
||||||
|
|
||||||
|
# Keep another reference to Ellipsis in the global namespace
|
||||||
|
# so it can be referenced in Ellipsis.__new__
|
||||||
|
# (The original "Ellipsis" name is removed from the global namespace later on)
|
||||||
|
_ast_Ellipsis = Ellipsis
|
||||||
|
|
||||||
_const_types = {
|
_const_types = {
|
||||||
Num: (int, float, complex),
|
Num: (int, float, complex),
|
||||||
Str: (str,),
|
Str: (str,),
|
||||||
@@ -636,10 +685,12 @@ class Param(expr_context):
|
|||||||
# We unparse those infinities to INFSTR.
|
# We unparse those infinities to INFSTR.
|
||||||
_INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
|
_INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
|
||||||
|
|
||||||
class _Precedence(IntEnum):
|
@_simple_enum(IntEnum)
|
||||||
|
class _Precedence:
|
||||||
"""Precedence table that originated from python grammar."""
|
"""Precedence table that originated from python grammar."""
|
||||||
|
|
||||||
TUPLE = auto()
|
NAMED_EXPR = auto() # <target> := <expr1>
|
||||||
|
TUPLE = auto() # <expr1>, <expr2>
|
||||||
YIELD = auto() # 'yield', 'yield from'
|
YIELD = auto() # 'yield', 'yield from'
|
||||||
TEST = auto() # 'if'-'else', 'lambda'
|
TEST = auto() # 'if'-'else', 'lambda'
|
||||||
OR = auto() # 'or'
|
OR = auto() # 'or'
|
||||||
@@ -677,11 +728,11 @@ class _Unparser(NodeVisitor):
|
|||||||
|
|
||||||
def __init__(self, *, _avoid_backslashes=False):
|
def __init__(self, *, _avoid_backslashes=False):
|
||||||
self._source = []
|
self._source = []
|
||||||
self._buffer = []
|
|
||||||
self._precedences = {}
|
self._precedences = {}
|
||||||
self._type_ignores = {}
|
self._type_ignores = {}
|
||||||
self._indent = 0
|
self._indent = 0
|
||||||
self._avoid_backslashes = _avoid_backslashes
|
self._avoid_backslashes = _avoid_backslashes
|
||||||
|
self._in_try_star = False
|
||||||
|
|
||||||
def interleave(self, inter, f, seq):
|
def interleave(self, inter, f, seq):
|
||||||
"""Call f on each item in seq, calling inter() in between."""
|
"""Call f on each item in seq, calling inter() in between."""
|
||||||
@@ -716,18 +767,19 @@ class _Unparser(NodeVisitor):
|
|||||||
self.maybe_newline()
|
self.maybe_newline()
|
||||||
self.write(" " * self._indent + text)
|
self.write(" " * self._indent + text)
|
||||||
|
|
||||||
def write(self, text):
|
def write(self, *text):
|
||||||
"""Append a piece of text"""
|
"""Add new source parts"""
|
||||||
self._source.append(text)
|
self._source.extend(text)
|
||||||
|
|
||||||
def buffer_writer(self, text):
|
@contextmanager
|
||||||
self._buffer.append(text)
|
def buffered(self, buffer = None):
|
||||||
|
if buffer is None:
|
||||||
|
buffer = []
|
||||||
|
|
||||||
@property
|
original_source = self._source
|
||||||
def buffer(self):
|
self._source = buffer
|
||||||
value = "".join(self._buffer)
|
yield buffer
|
||||||
self._buffer.clear()
|
self._source = original_source
|
||||||
return value
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def block(self, *, extra = None):
|
def block(self, *, extra = None):
|
||||||
@@ -837,7 +889,7 @@ class _Unparser(NodeVisitor):
|
|||||||
self.traverse(node.value)
|
self.traverse(node.value)
|
||||||
|
|
||||||
def visit_NamedExpr(self, node):
|
def visit_NamedExpr(self, node):
|
||||||
with self.require_parens(_Precedence.TUPLE, node):
|
with self.require_parens(_Precedence.NAMED_EXPR, node):
|
||||||
self.set_precedence(_Precedence.ATOM, node.target, node.value)
|
self.set_precedence(_Precedence.ATOM, node.target, node.value)
|
||||||
self.traverse(node.target)
|
self.traverse(node.target)
|
||||||
self.write(" := ")
|
self.write(" := ")
|
||||||
@@ -849,7 +901,7 @@ class _Unparser(NodeVisitor):
|
|||||||
|
|
||||||
def visit_ImportFrom(self, node):
|
def visit_ImportFrom(self, node):
|
||||||
self.fill("from ")
|
self.fill("from ")
|
||||||
self.write("." * node.level)
|
self.write("." * (node.level or 0))
|
||||||
if node.module:
|
if node.module:
|
||||||
self.write(node.module)
|
self.write(node.module)
|
||||||
self.write(" import ")
|
self.write(" import ")
|
||||||
@@ -858,6 +910,7 @@ class _Unparser(NodeVisitor):
|
|||||||
def visit_Assign(self, node):
|
def visit_Assign(self, node):
|
||||||
self.fill()
|
self.fill()
|
||||||
for target in node.targets:
|
for target in node.targets:
|
||||||
|
self.set_precedence(_Precedence.TUPLE, target)
|
||||||
self.traverse(target)
|
self.traverse(target)
|
||||||
self.write(" = ")
|
self.write(" = ")
|
||||||
self.traverse(node.value)
|
self.traverse(node.value)
|
||||||
@@ -950,7 +1003,7 @@ class _Unparser(NodeVisitor):
|
|||||||
self.write(" from ")
|
self.write(" from ")
|
||||||
self.traverse(node.cause)
|
self.traverse(node.cause)
|
||||||
|
|
||||||
def visit_Try(self, node):
|
def do_visit_try(self, node):
|
||||||
self.fill("try")
|
self.fill("try")
|
||||||
with self.block():
|
with self.block():
|
||||||
self.traverse(node.body)
|
self.traverse(node.body)
|
||||||
@@ -965,8 +1018,24 @@ class _Unparser(NodeVisitor):
|
|||||||
with self.block():
|
with self.block():
|
||||||
self.traverse(node.finalbody)
|
self.traverse(node.finalbody)
|
||||||
|
|
||||||
|
def visit_Try(self, node):
|
||||||
|
prev_in_try_star = self._in_try_star
|
||||||
|
try:
|
||||||
|
self._in_try_star = False
|
||||||
|
self.do_visit_try(node)
|
||||||
|
finally:
|
||||||
|
self._in_try_star = prev_in_try_star
|
||||||
|
|
||||||
|
def visit_TryStar(self, node):
|
||||||
|
prev_in_try_star = self._in_try_star
|
||||||
|
try:
|
||||||
|
self._in_try_star = True
|
||||||
|
self.do_visit_try(node)
|
||||||
|
finally:
|
||||||
|
self._in_try_star = prev_in_try_star
|
||||||
|
|
||||||
def visit_ExceptHandler(self, node):
|
def visit_ExceptHandler(self, node):
|
||||||
self.fill("except")
|
self.fill("except*" if self._in_try_star else "except")
|
||||||
if node.type:
|
if node.type:
|
||||||
self.write(" ")
|
self.write(" ")
|
||||||
self.traverse(node.type)
|
self.traverse(node.type)
|
||||||
@@ -982,6 +1051,8 @@ class _Unparser(NodeVisitor):
|
|||||||
self.fill("@")
|
self.fill("@")
|
||||||
self.traverse(deco)
|
self.traverse(deco)
|
||||||
self.fill("class " + node.name)
|
self.fill("class " + node.name)
|
||||||
|
if hasattr(node, "type_params"):
|
||||||
|
self._type_params_helper(node.type_params)
|
||||||
with self.delimit_if("(", ")", condition = node.bases or node.keywords):
|
with self.delimit_if("(", ")", condition = node.bases or node.keywords):
|
||||||
comma = False
|
comma = False
|
||||||
for e in node.bases:
|
for e in node.bases:
|
||||||
@@ -1013,6 +1084,8 @@ class _Unparser(NodeVisitor):
|
|||||||
self.traverse(deco)
|
self.traverse(deco)
|
||||||
def_str = fill_suffix + " " + node.name
|
def_str = fill_suffix + " " + node.name
|
||||||
self.fill(def_str)
|
self.fill(def_str)
|
||||||
|
if hasattr(node, "type_params"):
|
||||||
|
self._type_params_helper(node.type_params)
|
||||||
with self.delimit("(", ")"):
|
with self.delimit("(", ")"):
|
||||||
self.traverse(node.args)
|
self.traverse(node.args)
|
||||||
if node.returns:
|
if node.returns:
|
||||||
@@ -1021,6 +1094,30 @@ class _Unparser(NodeVisitor):
|
|||||||
with self.block(extra=self.get_type_comment(node)):
|
with self.block(extra=self.get_type_comment(node)):
|
||||||
self._write_docstring_and_traverse_body(node)
|
self._write_docstring_and_traverse_body(node)
|
||||||
|
|
||||||
|
def _type_params_helper(self, type_params):
|
||||||
|
if type_params is not None and len(type_params) > 0:
|
||||||
|
with self.delimit("[", "]"):
|
||||||
|
self.interleave(lambda: self.write(", "), self.traverse, type_params)
|
||||||
|
|
||||||
|
def visit_TypeVar(self, node):
|
||||||
|
self.write(node.name)
|
||||||
|
if node.bound:
|
||||||
|
self.write(": ")
|
||||||
|
self.traverse(node.bound)
|
||||||
|
|
||||||
|
def visit_TypeVarTuple(self, node):
|
||||||
|
self.write("*" + node.name)
|
||||||
|
|
||||||
|
def visit_ParamSpec(self, node):
|
||||||
|
self.write("**" + node.name)
|
||||||
|
|
||||||
|
def visit_TypeAlias(self, node):
|
||||||
|
self.fill("type ")
|
||||||
|
self.traverse(node.name)
|
||||||
|
self._type_params_helper(node.type_params)
|
||||||
|
self.write(" = ")
|
||||||
|
self.traverse(node.value)
|
||||||
|
|
||||||
def visit_For(self, node):
|
def visit_For(self, node):
|
||||||
self._for_helper("for ", node)
|
self._for_helper("for ", node)
|
||||||
|
|
||||||
@@ -1029,6 +1126,7 @@ class _Unparser(NodeVisitor):
|
|||||||
|
|
||||||
def _for_helper(self, fill, node):
|
def _for_helper(self, fill, node):
|
||||||
self.fill(fill)
|
self.fill(fill)
|
||||||
|
self.set_precedence(_Precedence.TUPLE, node.target)
|
||||||
self.traverse(node.target)
|
self.traverse(node.target)
|
||||||
self.write(" in ")
|
self.write(" in ")
|
||||||
self.traverse(node.iter)
|
self.traverse(node.iter)
|
||||||
@@ -1125,71 +1223,81 @@ class _Unparser(NodeVisitor):
|
|||||||
|
|
||||||
def visit_JoinedStr(self, node):
|
def visit_JoinedStr(self, node):
|
||||||
self.write("f")
|
self.write("f")
|
||||||
if self._avoid_backslashes:
|
|
||||||
self._fstring_JoinedStr(node, self.buffer_writer)
|
|
||||||
self._write_str_avoiding_backslashes(self.buffer)
|
|
||||||
return
|
|
||||||
|
|
||||||
# If we don't need to avoid backslashes globally (i.e., we only need
|
fstring_parts = []
|
||||||
# to avoid them inside FormattedValues), it's cosmetically preferred
|
|
||||||
# to use escaped whitespace. That is, it's preferred to use backslashes
|
|
||||||
# for cases like: f"{x}\n". To accomplish this, we keep track of what
|
|
||||||
# in our buffer corresponds to FormattedValues and what corresponds to
|
|
||||||
# Constant parts of the f-string, and allow escapes accordingly.
|
|
||||||
buffer = []
|
|
||||||
for value in node.values:
|
for value in node.values:
|
||||||
meth = getattr(self, "_fstring_" + type(value).__name__)
|
with self.buffered() as buffer:
|
||||||
meth(value, self.buffer_writer)
|
self._write_fstring_inner(value)
|
||||||
buffer.append((self.buffer, isinstance(value, Constant)))
|
fstring_parts.append(
|
||||||
new_buffer = []
|
("".join(buffer), isinstance(value, Constant))
|
||||||
quote_types = _ALL_QUOTES
|
|
||||||
for value, is_constant in buffer:
|
|
||||||
# Repeatedly narrow down the list of possible quote_types
|
|
||||||
value, quote_types = self._str_literal_helper(
|
|
||||||
value, quote_types=quote_types,
|
|
||||||
escape_special_whitespace=is_constant
|
|
||||||
)
|
)
|
||||||
new_buffer.append(value)
|
|
||||||
value = "".join(new_buffer)
|
new_fstring_parts = []
|
||||||
|
quote_types = list(_ALL_QUOTES)
|
||||||
|
fallback_to_repr = False
|
||||||
|
for value, is_constant in fstring_parts:
|
||||||
|
if is_constant:
|
||||||
|
value, new_quote_types = self._str_literal_helper(
|
||||||
|
value,
|
||||||
|
quote_types=quote_types,
|
||||||
|
escape_special_whitespace=True,
|
||||||
|
)
|
||||||
|
if set(new_quote_types).isdisjoint(quote_types):
|
||||||
|
fallback_to_repr = True
|
||||||
|
break
|
||||||
|
quote_types = new_quote_types
|
||||||
|
elif "\n" in value:
|
||||||
|
quote_types = [q for q in quote_types if q in _MULTI_QUOTES]
|
||||||
|
assert quote_types
|
||||||
|
new_fstring_parts.append(value)
|
||||||
|
|
||||||
|
if fallback_to_repr:
|
||||||
|
# If we weren't able to find a quote type that works for all parts
|
||||||
|
# of the JoinedStr, fallback to using repr and triple single quotes.
|
||||||
|
quote_types = ["'''"]
|
||||||
|
new_fstring_parts.clear()
|
||||||
|
for value, is_constant in fstring_parts:
|
||||||
|
if is_constant:
|
||||||
|
value = repr('"' + value) # force repr to use single quotes
|
||||||
|
expected_prefix = "'\""
|
||||||
|
assert value.startswith(expected_prefix), repr(value)
|
||||||
|
value = value[len(expected_prefix):-1]
|
||||||
|
new_fstring_parts.append(value)
|
||||||
|
|
||||||
|
value = "".join(new_fstring_parts)
|
||||||
quote_type = quote_types[0]
|
quote_type = quote_types[0]
|
||||||
self.write(f"{quote_type}{value}{quote_type}")
|
self.write(f"{quote_type}{value}{quote_type}")
|
||||||
|
|
||||||
|
def _write_fstring_inner(self, node):
|
||||||
|
if isinstance(node, JoinedStr):
|
||||||
|
# for both the f-string itself, and format_spec
|
||||||
|
for value in node.values:
|
||||||
|
self._write_fstring_inner(value)
|
||||||
|
elif isinstance(node, Constant) and isinstance(node.value, str):
|
||||||
|
value = node.value.replace("{", "{{").replace("}", "}}")
|
||||||
|
self.write(value)
|
||||||
|
elif isinstance(node, FormattedValue):
|
||||||
|
self.visit_FormattedValue(node)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unexpected node inside JoinedStr, {node!r}")
|
||||||
|
|
||||||
def visit_FormattedValue(self, node):
|
def visit_FormattedValue(self, node):
|
||||||
self.write("f")
|
def unparse_inner(inner):
|
||||||
self._fstring_FormattedValue(node, self.buffer_writer)
|
unparser = type(self)()
|
||||||
self._write_str_avoiding_backslashes(self.buffer)
|
unparser.set_precedence(_Precedence.TEST.next(), inner)
|
||||||
|
return unparser.visit(inner)
|
||||||
|
|
||||||
def _fstring_JoinedStr(self, node, write):
|
with self.delimit("{", "}"):
|
||||||
for value in node.values:
|
expr = unparse_inner(node.value)
|
||||||
meth = getattr(self, "_fstring_" + type(value).__name__)
|
if expr.startswith("{"):
|
||||||
meth(value, write)
|
# Separate pair of opening brackets as "{ {"
|
||||||
|
self.write(" ")
|
||||||
def _fstring_Constant(self, node, write):
|
self.write(expr)
|
||||||
if not isinstance(node.value, str):
|
if node.conversion != -1:
|
||||||
raise ValueError("Constants inside JoinedStr should be a string.")
|
self.write(f"!{chr(node.conversion)}")
|
||||||
value = node.value.replace("{", "{{").replace("}", "}}")
|
if node.format_spec:
|
||||||
write(value)
|
self.write(":")
|
||||||
|
self._write_fstring_inner(node.format_spec)
|
||||||
def _fstring_FormattedValue(self, node, write):
|
|
||||||
write("{")
|
|
||||||
unparser = type(self)(_avoid_backslashes=True)
|
|
||||||
unparser.set_precedence(_Precedence.TEST.next(), node.value)
|
|
||||||
expr = unparser.visit(node.value)
|
|
||||||
if expr.startswith("{"):
|
|
||||||
write(" ") # Separate pair of opening brackets as "{ {"
|
|
||||||
if "\\" in expr:
|
|
||||||
raise ValueError("Unable to avoid backslash in f-string expression part")
|
|
||||||
write(expr)
|
|
||||||
if node.conversion != -1:
|
|
||||||
conversion = chr(node.conversion)
|
|
||||||
if conversion not in "sra":
|
|
||||||
raise ValueError("Unknown f-string conversion.")
|
|
||||||
write(f"!{conversion}")
|
|
||||||
if node.format_spec:
|
|
||||||
write(":")
|
|
||||||
meth = getattr(self, "_fstring_" + type(node.format_spec).__name__)
|
|
||||||
meth(node.format_spec, write)
|
|
||||||
write("}")
|
|
||||||
|
|
||||||
def visit_Name(self, node):
|
def visit_Name(self, node):
|
||||||
self.write(node.id)
|
self.write(node.id)
|
||||||
@@ -1312,7 +1420,11 @@ class _Unparser(NodeVisitor):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def visit_Tuple(self, node):
|
def visit_Tuple(self, node):
|
||||||
with self.delimit("(", ")"):
|
with self.delimit_if(
|
||||||
|
"(",
|
||||||
|
")",
|
||||||
|
len(node.elts) == 0 or self.get_precedence(node) > _Precedence.TUPLE
|
||||||
|
):
|
||||||
self.items_view(self.traverse, node.elts)
|
self.items_view(self.traverse, node.elts)
|
||||||
|
|
||||||
unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"}
|
unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"}
|
||||||
@@ -1328,7 +1440,7 @@ class _Unparser(NodeVisitor):
|
|||||||
operator_precedence = self.unop_precedence[operator]
|
operator_precedence = self.unop_precedence[operator]
|
||||||
with self.require_parens(operator_precedence, node):
|
with self.require_parens(operator_precedence, node):
|
||||||
self.write(operator)
|
self.write(operator)
|
||||||
# factor prefixes (+, -, ~) shouldn't be seperated
|
# factor prefixes (+, -, ~) shouldn't be separated
|
||||||
# from the value they belong, (e.g: +1 instead of + 1)
|
# from the value they belong, (e.g: +1 instead of + 1)
|
||||||
if operator_precedence is not _Precedence.FACTOR:
|
if operator_precedence is not _Precedence.FACTOR:
|
||||||
self.write(" ")
|
self.write(" ")
|
||||||
@@ -1453,20 +1565,17 @@ class _Unparser(NodeVisitor):
|
|||||||
self.traverse(e)
|
self.traverse(e)
|
||||||
|
|
||||||
def visit_Subscript(self, node):
|
def visit_Subscript(self, node):
|
||||||
def is_simple_tuple(slice_value):
|
def is_non_empty_tuple(slice_value):
|
||||||
# when unparsing a non-empty tuple, the parentheses can be safely
|
|
||||||
# omitted if there aren't any elements that explicitly requires
|
|
||||||
# parentheses (such as starred expressions).
|
|
||||||
return (
|
return (
|
||||||
isinstance(slice_value, Tuple)
|
isinstance(slice_value, Tuple)
|
||||||
and slice_value.elts
|
and slice_value.elts
|
||||||
and not any(isinstance(elt, Starred) for elt in slice_value.elts)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.set_precedence(_Precedence.ATOM, node.value)
|
self.set_precedence(_Precedence.ATOM, node.value)
|
||||||
self.traverse(node.value)
|
self.traverse(node.value)
|
||||||
with self.delimit("[", "]"):
|
with self.delimit("[", "]"):
|
||||||
if is_simple_tuple(node.slice):
|
if is_non_empty_tuple(node.slice):
|
||||||
|
# parentheses can be omitted if the tuple isn't empty
|
||||||
self.items_view(self.traverse, node.slice.elts)
|
self.items_view(self.traverse, node.slice.elts)
|
||||||
else:
|
else:
|
||||||
self.traverse(node.slice)
|
self.traverse(node.slice)
|
||||||
@@ -1563,8 +1672,11 @@ class _Unparser(NodeVisitor):
|
|||||||
|
|
||||||
def visit_Lambda(self, node):
|
def visit_Lambda(self, node):
|
||||||
with self.require_parens(_Precedence.TEST, node):
|
with self.require_parens(_Precedence.TEST, node):
|
||||||
self.write("lambda ")
|
self.write("lambda")
|
||||||
self.traverse(node.args)
|
with self.buffered() as buffer:
|
||||||
|
self.traverse(node.args)
|
||||||
|
if buffer:
|
||||||
|
self.write(" ", *buffer)
|
||||||
self.write(": ")
|
self.write(": ")
|
||||||
self.set_precedence(_Precedence.TEST, node.body)
|
self.set_precedence(_Precedence.TEST, node.body)
|
||||||
self.traverse(node.body)
|
self.traverse(node.body)
|
||||||
@@ -1673,6 +1785,22 @@ def unparse(ast_obj):
|
|||||||
return unparser.visit(ast_obj)
|
return unparser.visit(ast_obj)
|
||||||
|
|
||||||
|
|
||||||
|
_deprecated_globals = {
|
||||||
|
name: globals().pop(name)
|
||||||
|
for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis')
|
||||||
|
}
|
||||||
|
|
||||||
|
def __getattr__(name):
|
||||||
|
if name in _deprecated_globals:
|
||||||
|
globals()[name] = value = _deprecated_globals[name]
|
||||||
|
import warnings
|
||||||
|
warnings._deprecated(
|
||||||
|
f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
raise AttributeError(f"module 'ast' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|||||||
19
Lib/asyncio/__init__.py
vendored
19
Lib/asyncio/__init__.py
vendored
@@ -1,22 +1,14 @@
|
|||||||
"""The asyncio package, tracking PEP 3156."""
|
"""The asyncio package, tracking PEP 3156."""
|
||||||
|
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import selectors
|
|
||||||
# XXX RustPython TODO: _overlapped
|
|
||||||
if sys.platform == 'win32' and False:
|
|
||||||
# Similar thing for _overlapped.
|
|
||||||
try:
|
|
||||||
from . import _overlapped
|
|
||||||
except ImportError:
|
|
||||||
import _overlapped # Will also be exported.
|
|
||||||
|
|
||||||
|
|
||||||
# This relies on each of the submodules having an __all__ variable.
|
# This relies on each of the submodules having an __all__ variable.
|
||||||
from .base_events import *
|
from .base_events import *
|
||||||
from .coroutines import *
|
from .coroutines import *
|
||||||
from .events import *
|
from .events import *
|
||||||
|
from .exceptions import *
|
||||||
from .futures import *
|
from .futures import *
|
||||||
from .locks import *
|
from .locks import *
|
||||||
from .protocols import *
|
from .protocols import *
|
||||||
@@ -25,11 +17,15 @@ from .queues import *
|
|||||||
from .streams import *
|
from .streams import *
|
||||||
from .subprocess import *
|
from .subprocess import *
|
||||||
from .tasks import *
|
from .tasks import *
|
||||||
|
from .taskgroups import *
|
||||||
|
from .timeouts import *
|
||||||
|
from .threads import *
|
||||||
from .transports import *
|
from .transports import *
|
||||||
|
|
||||||
__all__ = (base_events.__all__ +
|
__all__ = (base_events.__all__ +
|
||||||
coroutines.__all__ +
|
coroutines.__all__ +
|
||||||
events.__all__ +
|
events.__all__ +
|
||||||
|
exceptions.__all__ +
|
||||||
futures.__all__ +
|
futures.__all__ +
|
||||||
locks.__all__ +
|
locks.__all__ +
|
||||||
protocols.__all__ +
|
protocols.__all__ +
|
||||||
@@ -38,6 +34,9 @@ __all__ = (base_events.__all__ +
|
|||||||
streams.__all__ +
|
streams.__all__ +
|
||||||
subprocess.__all__ +
|
subprocess.__all__ +
|
||||||
tasks.__all__ +
|
tasks.__all__ +
|
||||||
|
taskgroups.__all__ +
|
||||||
|
threads.__all__ +
|
||||||
|
timeouts.__all__ +
|
||||||
transports.__all__)
|
transports.__all__)
|
||||||
|
|
||||||
if sys.platform == 'win32': # pragma: no cover
|
if sys.platform == 'win32': # pragma: no cover
|
||||||
|
|||||||
125
Lib/asyncio/__main__.py
vendored
Normal file
125
Lib/asyncio/__main__.py
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import ast
|
||||||
|
import asyncio
|
||||||
|
import code
|
||||||
|
import concurrent.futures
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import types
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from . import futures
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncIOInteractiveConsole(code.InteractiveConsole):
|
||||||
|
|
||||||
|
def __init__(self, locals, loop):
|
||||||
|
super().__init__(locals)
|
||||||
|
self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
|
||||||
|
|
||||||
|
self.loop = loop
|
||||||
|
|
||||||
|
def runcode(self, code):
|
||||||
|
future = concurrent.futures.Future()
|
||||||
|
|
||||||
|
def callback():
|
||||||
|
global repl_future
|
||||||
|
global repl_future_interrupted
|
||||||
|
|
||||||
|
repl_future = None
|
||||||
|
repl_future_interrupted = False
|
||||||
|
|
||||||
|
func = types.FunctionType(code, self.locals)
|
||||||
|
try:
|
||||||
|
coro = func()
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except KeyboardInterrupt as ex:
|
||||||
|
repl_future_interrupted = True
|
||||||
|
future.set_exception(ex)
|
||||||
|
return
|
||||||
|
except BaseException as ex:
|
||||||
|
future.set_exception(ex)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not inspect.iscoroutine(coro):
|
||||||
|
future.set_result(coro)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
repl_future = self.loop.create_task(coro)
|
||||||
|
futures._chain_future(repl_future, future)
|
||||||
|
except BaseException as exc:
|
||||||
|
future.set_exception(exc)
|
||||||
|
|
||||||
|
loop.call_soon_threadsafe(callback)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return future.result()
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except BaseException:
|
||||||
|
if repl_future_interrupted:
|
||||||
|
self.write("\nKeyboardInterrupt\n")
|
||||||
|
else:
|
||||||
|
self.showtraceback()
|
||||||
|
|
||||||
|
|
||||||
|
class REPLThread(threading.Thread):
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
banner = (
|
||||||
|
f'asyncio REPL {sys.version} on {sys.platform}\n'
|
||||||
|
f'Use "await" directly instead of "asyncio.run()".\n'
|
||||||
|
f'Type "help", "copyright", "credits" or "license" '
|
||||||
|
f'for more information.\n'
|
||||||
|
f'{getattr(sys, "ps1", ">>> ")}import asyncio'
|
||||||
|
)
|
||||||
|
|
||||||
|
console.interact(
|
||||||
|
banner=banner,
|
||||||
|
exitmsg='exiting asyncio REPL...')
|
||||||
|
finally:
|
||||||
|
warnings.filterwarnings(
|
||||||
|
'ignore',
|
||||||
|
message=r'^coroutine .* was never awaited$',
|
||||||
|
category=RuntimeWarning)
|
||||||
|
|
||||||
|
loop.call_soon_threadsafe(loop.stop)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
repl_locals = {'asyncio': asyncio}
|
||||||
|
for key in {'__name__', '__package__',
|
||||||
|
'__loader__', '__spec__',
|
||||||
|
'__builtins__', '__file__'}:
|
||||||
|
repl_locals[key] = locals()[key]
|
||||||
|
|
||||||
|
console = AsyncIOInteractiveConsole(repl_locals, loop)
|
||||||
|
|
||||||
|
repl_future = None
|
||||||
|
repl_future_interrupted = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import readline # NoQA
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
repl_thread = REPLThread()
|
||||||
|
repl_thread.daemon = True
|
||||||
|
repl_thread.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
if repl_future and not repl_future.done():
|
||||||
|
repl_future.cancel()
|
||||||
|
repl_future_interrupted = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
1356
Lib/asyncio/base_events.py
vendored
1356
Lib/asyncio/base_events.py
vendored
File diff suppressed because it is too large
Load Diff
38
Lib/asyncio/base_futures.py
vendored
38
Lib/asyncio/base_futures.py
vendored
@@ -1,18 +1,8 @@
|
|||||||
__all__ = []
|
__all__ = ()
|
||||||
|
|
||||||
import concurrent.futures._base
|
|
||||||
import reprlib
|
import reprlib
|
||||||
|
|
||||||
from . import events
|
from . import format_helpers
|
||||||
|
|
||||||
Error = concurrent.futures._base.Error
|
|
||||||
CancelledError = concurrent.futures.CancelledError
|
|
||||||
TimeoutError = concurrent.futures.TimeoutError
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidStateError(Error):
|
|
||||||
"""The operation is not allowed in this state."""
|
|
||||||
|
|
||||||
|
|
||||||
# States for Future.
|
# States for Future.
|
||||||
_PENDING = 'PENDING'
|
_PENDING = 'PENDING'
|
||||||
@@ -38,17 +28,17 @@ def _format_callbacks(cb):
|
|||||||
cb = ''
|
cb = ''
|
||||||
|
|
||||||
def format_cb(callback):
|
def format_cb(callback):
|
||||||
return events._format_callback_source(callback, ())
|
return format_helpers._format_callback_source(callback, ())
|
||||||
|
|
||||||
if size == 1:
|
if size == 1:
|
||||||
cb = format_cb(cb[0])
|
cb = format_cb(cb[0][0])
|
||||||
elif size == 2:
|
elif size == 2:
|
||||||
cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
|
cb = '{}, {}'.format(format_cb(cb[0][0]), format_cb(cb[1][0]))
|
||||||
elif size > 2:
|
elif size > 2:
|
||||||
cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
|
cb = '{}, <{} more>, {}'.format(format_cb(cb[0][0]),
|
||||||
size - 2,
|
size - 2,
|
||||||
format_cb(cb[-1]))
|
format_cb(cb[-1][0]))
|
||||||
return 'cb=[%s]' % cb
|
return f'cb=[{cb}]'
|
||||||
|
|
||||||
|
|
||||||
def _future_repr_info(future):
|
def _future_repr_info(future):
|
||||||
@@ -57,15 +47,21 @@ def _future_repr_info(future):
|
|||||||
info = [future._state.lower()]
|
info = [future._state.lower()]
|
||||||
if future._state == _FINISHED:
|
if future._state == _FINISHED:
|
||||||
if future._exception is not None:
|
if future._exception is not None:
|
||||||
info.append('exception={!r}'.format(future._exception))
|
info.append(f'exception={future._exception!r}')
|
||||||
else:
|
else:
|
||||||
# use reprlib to limit the length of the output, especially
|
# use reprlib to limit the length of the output, especially
|
||||||
# for very long strings
|
# for very long strings
|
||||||
result = reprlib.repr(future._result)
|
result = reprlib.repr(future._result)
|
||||||
info.append('result={}'.format(result))
|
info.append(f'result={result}')
|
||||||
if future._callbacks:
|
if future._callbacks:
|
||||||
info.append(_format_callbacks(future._callbacks))
|
info.append(_format_callbacks(future._callbacks))
|
||||||
if future._source_traceback:
|
if future._source_traceback:
|
||||||
frame = future._source_traceback[-1]
|
frame = future._source_traceback[-1]
|
||||||
info.append('created at %s:%s' % (frame[0], frame[1]))
|
info.append(f'created at {frame[0]}:{frame[1]}')
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
@reprlib.recursive_repr()
|
||||||
|
def _future_repr(future):
|
||||||
|
info = ' '.join(_future_repr_info(future))
|
||||||
|
return f'<{future.__class__.__name__} {info}>'
|
||||||
|
|||||||
78
Lib/asyncio/base_subprocess.py
vendored
78
Lib/asyncio/base_subprocess.py
vendored
@@ -2,10 +2,8 @@ import collections
|
|||||||
import subprocess
|
import subprocess
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import protocols
|
from . import protocols
|
||||||
from . import transports
|
from . import transports
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -59,9 +57,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
if self._closed:
|
if self._closed:
|
||||||
info.append('closed')
|
info.append('closed')
|
||||||
if self._pid is not None:
|
if self._pid is not None:
|
||||||
info.append('pid=%s' % self._pid)
|
info.append(f'pid={self._pid}')
|
||||||
if self._returncode is not None:
|
if self._returncode is not None:
|
||||||
info.append('returncode=%s' % self._returncode)
|
info.append(f'returncode={self._returncode}')
|
||||||
elif self._pid is not None:
|
elif self._pid is not None:
|
||||||
info.append('running')
|
info.append('running')
|
||||||
else:
|
else:
|
||||||
@@ -69,19 +67,19 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
|
|
||||||
stdin = self._pipes.get(0)
|
stdin = self._pipes.get(0)
|
||||||
if stdin is not None:
|
if stdin is not None:
|
||||||
info.append('stdin=%s' % stdin.pipe)
|
info.append(f'stdin={stdin.pipe}')
|
||||||
|
|
||||||
stdout = self._pipes.get(1)
|
stdout = self._pipes.get(1)
|
||||||
stderr = self._pipes.get(2)
|
stderr = self._pipes.get(2)
|
||||||
if stdout is not None and stderr is stdout:
|
if stdout is not None and stderr is stdout:
|
||||||
info.append('stdout=stderr=%s' % stdout.pipe)
|
info.append(f'stdout=stderr={stdout.pipe}')
|
||||||
else:
|
else:
|
||||||
if stdout is not None:
|
if stdout is not None:
|
||||||
info.append('stdout=%s' % stdout.pipe)
|
info.append(f'stdout={stdout.pipe}')
|
||||||
if stderr is not None:
|
if stderr is not None:
|
||||||
info.append('stderr=%s' % stderr.pipe)
|
info.append(f'stderr={stderr.pipe}')
|
||||||
|
|
||||||
return '<%s>' % ' '.join(info)
|
return '<{}>'.format(' '.join(info))
|
||||||
|
|
||||||
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
|
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -105,12 +103,13 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
continue
|
continue
|
||||||
proto.pipe.close()
|
proto.pipe.close()
|
||||||
|
|
||||||
if (self._proc is not None
|
if (self._proc is not None and
|
||||||
# the child process finished?
|
# has the child process finished?
|
||||||
and self._returncode is None
|
self._returncode is None and
|
||||||
# the child process finished but the transport was not notified yet?
|
# the child process has finished, but the
|
||||||
and self._proc.poll() is None
|
# transport hasn't been notified yet?
|
||||||
):
|
self._proc.poll() is None):
|
||||||
|
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
logger.warning('Close running child process: kill %r', self)
|
logger.warning('Close running child process: kill %r', self)
|
||||||
|
|
||||||
@@ -121,15 +120,10 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
|
|
||||||
# Don't clear the _proc reference yet: _post_init() may still run
|
# Don't clear the _proc reference yet: _post_init() may still run
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
def __del__(self, _warn=warnings.warn):
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
if not self._closed:
|
||||||
# to the PEP 442.
|
_warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
|
||||||
if compat.PY34:
|
self.close()
|
||||||
def __del__(self):
|
|
||||||
if not self._closed:
|
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
|
||||||
source=self)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def get_pid(self):
|
def get_pid(self):
|
||||||
return self._pid
|
return self._pid
|
||||||
@@ -159,26 +153,25 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
self._check_proc()
|
self._check_proc()
|
||||||
self._proc.kill()
|
self._proc.kill()
|
||||||
|
|
||||||
@coroutine
|
async def _connect_pipes(self, waiter):
|
||||||
def _connect_pipes(self, waiter):
|
|
||||||
try:
|
try:
|
||||||
proc = self._proc
|
proc = self._proc
|
||||||
loop = self._loop
|
loop = self._loop
|
||||||
|
|
||||||
if proc.stdin is not None:
|
if proc.stdin is not None:
|
||||||
_, pipe = yield from loop.connect_write_pipe(
|
_, pipe = await loop.connect_write_pipe(
|
||||||
lambda: WriteSubprocessPipeProto(self, 0),
|
lambda: WriteSubprocessPipeProto(self, 0),
|
||||||
proc.stdin)
|
proc.stdin)
|
||||||
self._pipes[0] = pipe
|
self._pipes[0] = pipe
|
||||||
|
|
||||||
if proc.stdout is not None:
|
if proc.stdout is not None:
|
||||||
_, pipe = yield from loop.connect_read_pipe(
|
_, pipe = await loop.connect_read_pipe(
|
||||||
lambda: ReadSubprocessPipeProto(self, 1),
|
lambda: ReadSubprocessPipeProto(self, 1),
|
||||||
proc.stdout)
|
proc.stdout)
|
||||||
self._pipes[1] = pipe
|
self._pipes[1] = pipe
|
||||||
|
|
||||||
if proc.stderr is not None:
|
if proc.stderr is not None:
|
||||||
_, pipe = yield from loop.connect_read_pipe(
|
_, pipe = await loop.connect_read_pipe(
|
||||||
lambda: ReadSubprocessPipeProto(self, 2),
|
lambda: ReadSubprocessPipeProto(self, 2),
|
||||||
proc.stderr)
|
proc.stderr)
|
||||||
self._pipes[2] = pipe
|
self._pipes[2] = pipe
|
||||||
@@ -189,7 +182,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
for callback, data in self._pending_calls:
|
for callback, data in self._pending_calls:
|
||||||
loop.call_soon(callback, *data)
|
loop.call_soon(callback, *data)
|
||||||
self._pending_calls = None
|
self._pending_calls = None
|
||||||
except Exception as exc:
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
if waiter is not None and not waiter.cancelled():
|
if waiter is not None and not waiter.cancelled():
|
||||||
waiter.set_exception(exc)
|
waiter.set_exception(exc)
|
||||||
else:
|
else:
|
||||||
@@ -213,24 +208,17 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
assert returncode is not None, returncode
|
assert returncode is not None, returncode
|
||||||
assert self._returncode is None, self._returncode
|
assert self._returncode is None, self._returncode
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
logger.info('%r exited with return code %r',
|
logger.info('%r exited with return code %r', self, returncode)
|
||||||
self, returncode)
|
|
||||||
self._returncode = returncode
|
self._returncode = returncode
|
||||||
if self._proc.returncode is None:
|
if self._proc.returncode is None:
|
||||||
# asyncio uses a child watcher: copy the status into the Popen
|
# asyncio uses a child watcher: copy the status into the Popen
|
||||||
# object. On Python 3.6, it is required to avoid a ResourceWarning.
|
# object. On Python 3.6, it is required to avoid a ResourceWarning.
|
||||||
self._proc.returncode = returncode
|
self._proc.returncode = returncode
|
||||||
self._call(self._protocol.process_exited)
|
self._call(self._protocol.process_exited)
|
||||||
|
|
||||||
self._try_finish()
|
self._try_finish()
|
||||||
|
|
||||||
# wake up futures waiting for wait()
|
async def _wait(self):
|
||||||
for waiter in self._exit_waiters:
|
|
||||||
if not waiter.cancelled():
|
|
||||||
waiter.set_result(returncode)
|
|
||||||
self._exit_waiters = None
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _wait(self):
|
|
||||||
"""Wait until the process exit and return the process return code.
|
"""Wait until the process exit and return the process return code.
|
||||||
|
|
||||||
This method is a coroutine."""
|
This method is a coroutine."""
|
||||||
@@ -239,7 +227,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
|
|
||||||
waiter = self._loop.create_future()
|
waiter = self._loop.create_future()
|
||||||
self._exit_waiters.append(waiter)
|
self._exit_waiters.append(waiter)
|
||||||
return (yield from waiter)
|
return await waiter
|
||||||
|
|
||||||
def _try_finish(self):
|
def _try_finish(self):
|
||||||
assert not self._finished
|
assert not self._finished
|
||||||
@@ -254,6 +242,11 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
|||||||
try:
|
try:
|
||||||
self._protocol.connection_lost(exc)
|
self._protocol.connection_lost(exc)
|
||||||
finally:
|
finally:
|
||||||
|
# wake up futures waiting for wait()
|
||||||
|
for waiter in self._exit_waiters:
|
||||||
|
if not waiter.cancelled():
|
||||||
|
waiter.set_result(self._returncode)
|
||||||
|
self._exit_waiters = None
|
||||||
self._loop = None
|
self._loop = None
|
||||||
self._proc = None
|
self._proc = None
|
||||||
self._protocol = None
|
self._protocol = None
|
||||||
@@ -271,8 +264,7 @@ class WriteSubprocessPipeProto(protocols.BaseProtocol):
|
|||||||
self.pipe = transport
|
self.pipe = transport
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('<%s fd=%s pipe=%r>'
|
return f'<{self.__class__.__name__} fd={self.fd} pipe={self.pipe!r}>'
|
||||||
% (self.__class__.__name__, self.fd, self.pipe))
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
self.disconnected = True
|
self.disconnected = True
|
||||||
|
|||||||
42
Lib/asyncio/base_tasks.py
vendored
42
Lib/asyncio/base_tasks.py
vendored
@@ -1,4 +1,5 @@
|
|||||||
import linecache
|
import linecache
|
||||||
|
import reprlib
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from . import base_futures
|
from . import base_futures
|
||||||
@@ -8,25 +9,42 @@ from . import coroutines
|
|||||||
def _task_repr_info(task):
|
def _task_repr_info(task):
|
||||||
info = base_futures._future_repr_info(task)
|
info = base_futures._future_repr_info(task)
|
||||||
|
|
||||||
if task._must_cancel:
|
if task.cancelling() and not task.done():
|
||||||
# replace status
|
# replace status
|
||||||
info[0] = 'cancelling'
|
info[0] = 'cancelling'
|
||||||
|
|
||||||
coro = coroutines._format_coroutine(task._coro)
|
info.insert(1, 'name=%r' % task.get_name())
|
||||||
info.insert(1, 'coro=<%s>' % coro)
|
|
||||||
|
|
||||||
if task._fut_waiter is not None:
|
if task._fut_waiter is not None:
|
||||||
info.insert(2, 'wait_for=%r' % task._fut_waiter)
|
info.insert(2, f'wait_for={task._fut_waiter!r}')
|
||||||
|
|
||||||
|
if task._coro:
|
||||||
|
coro = coroutines._format_coroutine(task._coro)
|
||||||
|
info.insert(2, f'coro=<{coro}>')
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
@reprlib.recursive_repr()
|
||||||
|
def _task_repr(task):
|
||||||
|
info = ' '.join(_task_repr_info(task))
|
||||||
|
return f'<{task.__class__.__name__} {info}>'
|
||||||
|
|
||||||
|
|
||||||
def _task_get_stack(task, limit):
|
def _task_get_stack(task, limit):
|
||||||
frames = []
|
frames = []
|
||||||
try:
|
if hasattr(task._coro, 'cr_frame'):
|
||||||
# 'async def' coroutines
|
# case 1: 'async def' coroutines
|
||||||
f = task._coro.cr_frame
|
f = task._coro.cr_frame
|
||||||
except AttributeError:
|
elif hasattr(task._coro, 'gi_frame'):
|
||||||
|
# case 2: legacy coroutines
|
||||||
f = task._coro.gi_frame
|
f = task._coro.gi_frame
|
||||||
|
elif hasattr(task._coro, 'ag_frame'):
|
||||||
|
# case 3: async generators
|
||||||
|
f = task._coro.ag_frame
|
||||||
|
else:
|
||||||
|
# case 4: unknown objects
|
||||||
|
f = None
|
||||||
if f is not None:
|
if f is not None:
|
||||||
while f is not None:
|
while f is not None:
|
||||||
if limit is not None:
|
if limit is not None:
|
||||||
@@ -61,15 +79,15 @@ def _task_print_stack(task, limit, file):
|
|||||||
linecache.checkcache(filename)
|
linecache.checkcache(filename)
|
||||||
line = linecache.getline(filename, lineno, f.f_globals)
|
line = linecache.getline(filename, lineno, f.f_globals)
|
||||||
extracted_list.append((filename, lineno, name, line))
|
extracted_list.append((filename, lineno, name, line))
|
||||||
|
|
||||||
exc = task._exception
|
exc = task._exception
|
||||||
if not extracted_list:
|
if not extracted_list:
|
||||||
print('No stack for %r' % task, file=file)
|
print(f'No stack for {task!r}', file=file)
|
||||||
elif exc is not None:
|
elif exc is not None:
|
||||||
print('Traceback for %r (most recent call last):' % task,
|
print(f'Traceback for {task!r} (most recent call last):', file=file)
|
||||||
file=file)
|
|
||||||
else:
|
else:
|
||||||
print('Stack for %r (most recent call last):' % task,
|
print(f'Stack for {task!r} (most recent call last):', file=file)
|
||||||
file=file)
|
|
||||||
traceback.print_list(extracted_list, file=file)
|
traceback.print_list(extracted_list, file=file)
|
||||||
if exc is not None:
|
if exc is not None:
|
||||||
for line in traceback.format_exception_only(exc.__class__, exc):
|
for line in traceback.format_exception_only(exc.__class__, exc):
|
||||||
|
|||||||
18
Lib/asyncio/compat.py
vendored
18
Lib/asyncio/compat.py
vendored
@@ -1,18 +0,0 @@
|
|||||||
"""Compatibility helpers for the different Python versions."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
PY34 = sys.version_info >= (3, 4)
|
|
||||||
PY35 = sys.version_info >= (3, 5)
|
|
||||||
PY352 = sys.version_info >= (3, 5, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def flatten_list_bytes(list_of_data):
|
|
||||||
"""Concatenate a sequence of bytes-like objects."""
|
|
||||||
if not PY34:
|
|
||||||
# On Python 3.3 and older, bytes.join() doesn't handle
|
|
||||||
# memoryview.
|
|
||||||
list_of_data = (
|
|
||||||
bytes(data) if isinstance(data, memoryview) else data
|
|
||||||
for data in list_of_data)
|
|
||||||
return b''.join(list_of_data)
|
|
||||||
36
Lib/asyncio/constants.py
vendored
36
Lib/asyncio/constants.py
vendored
@@ -1,7 +1,41 @@
|
|||||||
"""Constants."""
|
# Contains code from https://github.com/MagicStack/uvloop/tree/v0.16.0
|
||||||
|
# SPDX-License-Identifier: PSF-2.0 AND (MIT OR Apache-2.0)
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2015-2021 MagicStack Inc. http://magic.io
|
||||||
|
|
||||||
|
import enum
|
||||||
|
|
||||||
# After the connection is lost, log warnings after this many write()s.
|
# After the connection is lost, log warnings after this many write()s.
|
||||||
LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
|
LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
|
||||||
|
|
||||||
# Seconds to wait before retrying accept().
|
# Seconds to wait before retrying accept().
|
||||||
ACCEPT_RETRY_DELAY = 1
|
ACCEPT_RETRY_DELAY = 1
|
||||||
|
|
||||||
|
# Number of stack entries to capture in debug mode.
|
||||||
|
# The larger the number, the slower the operation in debug mode
|
||||||
|
# (see extract_stack() in format_helpers.py).
|
||||||
|
DEBUG_STACK_DEPTH = 10
|
||||||
|
|
||||||
|
# Number of seconds to wait for SSL handshake to complete
|
||||||
|
# The default timeout matches that of Nginx.
|
||||||
|
SSL_HANDSHAKE_TIMEOUT = 60.0
|
||||||
|
|
||||||
|
# Number of seconds to wait for SSL shutdown to complete
|
||||||
|
# The default timeout mimics lingering_time
|
||||||
|
SSL_SHUTDOWN_TIMEOUT = 30.0
|
||||||
|
|
||||||
|
# Used in sendfile fallback code. We use fallback for platforms
|
||||||
|
# that don't support sendfile, or for TLS connections.
|
||||||
|
SENDFILE_FALLBACK_READBUFFER_SIZE = 1024 * 256
|
||||||
|
|
||||||
|
FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB
|
||||||
|
FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB
|
||||||
|
|
||||||
|
# Default timeout for joining the threads in the threadpool
|
||||||
|
THREAD_JOIN_TIMEOUT = 300
|
||||||
|
|
||||||
|
# The enum should be here to break circular dependencies between
|
||||||
|
# base_events and sslproto
|
||||||
|
class _SendfileMode(enum.Enum):
|
||||||
|
UNSUPPORTED = enum.auto()
|
||||||
|
TRY_NATIVE = enum.auto()
|
||||||
|
FALLBACK = enum.auto()
|
||||||
|
|||||||
365
Lib/asyncio/coroutines.py
vendored
365
Lib/asyncio/coroutines.py
vendored
@@ -1,249 +1,16 @@
|
|||||||
__all__ = ['coroutine',
|
__all__ = 'iscoroutinefunction', 'iscoroutine'
|
||||||
'iscoroutinefunction', 'iscoroutine']
|
|
||||||
|
|
||||||
import functools
|
import collections.abc
|
||||||
import inspect
|
import inspect
|
||||||
import opcode
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import base_futures
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
def _is_debug_mode():
|
||||||
# Opcode of "yield from" instruction
|
# See: https://docs.python.org/3/library/asyncio-dev.html#asyncio-debug-mode.
|
||||||
_YIELD_FROM = opcode.opmap['YIELD_FROM']
|
return sys.flags.dev_mode or (not sys.flags.ignore_environment and
|
||||||
|
bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
||||||
# If you set _DEBUG to true, @coroutine will wrap the resulting
|
|
||||||
# generator objects in a CoroWrapper instance (defined below). That
|
|
||||||
# instance will log a message when the generator is never iterated
|
|
||||||
# over, which may happen when you forget to use "yield from" with a
|
|
||||||
# coroutine call. Note that the value of the _DEBUG flag is taken
|
|
||||||
# when the decorator is used, so to be of any use it must be set
|
|
||||||
# before you define your coroutines. A downside of using this feature
|
|
||||||
# is that tracebacks show entries for the CoroWrapper.__next__ method
|
|
||||||
# when _DEBUG is true.
|
|
||||||
_DEBUG = (not sys.flags.ignore_environment and
|
|
||||||
bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
_types_coroutine = types.coroutine
|
|
||||||
_types_CoroutineType = types.CoroutineType
|
|
||||||
except AttributeError:
|
|
||||||
# Python 3.4
|
|
||||||
_types_coroutine = None
|
|
||||||
_types_CoroutineType = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
_inspect_iscoroutinefunction = inspect.iscoroutinefunction
|
|
||||||
except AttributeError:
|
|
||||||
# Python 3.4
|
|
||||||
_inspect_iscoroutinefunction = lambda func: False
|
|
||||||
|
|
||||||
try:
|
|
||||||
from collections.abc import Coroutine as _CoroutineABC, \
|
|
||||||
Awaitable as _AwaitableABC
|
|
||||||
except ImportError:
|
|
||||||
_CoroutineABC = _AwaitableABC = None
|
|
||||||
|
|
||||||
|
|
||||||
# Check for CPython issue #21209
|
|
||||||
def has_yield_from_bug():
|
|
||||||
class MyGen:
|
|
||||||
def __init__(self):
|
|
||||||
self.send_args = None
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
def __next__(self):
|
|
||||||
return 42
|
|
||||||
def send(self, *what):
|
|
||||||
self.send_args = what
|
|
||||||
return None
|
|
||||||
def yield_from_gen(gen):
|
|
||||||
yield from gen
|
|
||||||
value = (1, 2, 3)
|
|
||||||
gen = MyGen()
|
|
||||||
coro = yield_from_gen(gen)
|
|
||||||
next(coro)
|
|
||||||
coro.send(value)
|
|
||||||
return gen.send_args != (value,)
|
|
||||||
_YIELD_FROM_BUG = has_yield_from_bug()
|
|
||||||
del has_yield_from_bug
|
|
||||||
|
|
||||||
|
|
||||||
def debug_wrapper(gen):
|
|
||||||
# This function is called from 'sys.set_coroutine_wrapper'.
|
|
||||||
# We only wrap here coroutines defined via 'async def' syntax.
|
|
||||||
# Generator-based coroutines are wrapped in @coroutine
|
|
||||||
# decorator.
|
|
||||||
return CoroWrapper(gen, None)
|
|
||||||
|
|
||||||
|
|
||||||
class CoroWrapper:
|
|
||||||
# Wrapper for coroutine object in _DEBUG mode.
|
|
||||||
|
|
||||||
def __init__(self, gen, func=None):
|
|
||||||
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
|
|
||||||
self.gen = gen
|
|
||||||
self.func = func # Used to unwrap @coroutine decorator
|
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
|
||||||
self.__name__ = getattr(gen, '__name__', None)
|
|
||||||
self.__qualname__ = getattr(gen, '__qualname__', None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
coro_repr = _format_coroutine(self)
|
|
||||||
if self._source_traceback:
|
|
||||||
frame = self._source_traceback[-1]
|
|
||||||
coro_repr += ', created at %s:%s' % (frame[0], frame[1])
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, coro_repr)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
return self.gen.send(None)
|
|
||||||
|
|
||||||
if _YIELD_FROM_BUG:
|
|
||||||
# For for CPython issue #21209: using "yield from" and a custom
|
|
||||||
# generator, generator.send(tuple) unpacks the tuple instead of passing
|
|
||||||
# the tuple unchanged. Check if the caller is a generator using "yield
|
|
||||||
# from" to decide if the parameter should be unpacked or not.
|
|
||||||
def send(self, *value):
|
|
||||||
frame = sys._getframe()
|
|
||||||
caller = frame.f_back
|
|
||||||
assert caller.f_lasti >= 0
|
|
||||||
if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
|
|
||||||
value = value[0]
|
|
||||||
return self.gen.send(value)
|
|
||||||
else:
|
|
||||||
def send(self, value):
|
|
||||||
return self.gen.send(value)
|
|
||||||
|
|
||||||
def throw(self, type, value=None, traceback=None):
|
|
||||||
return self.gen.throw(type, value, traceback)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
return self.gen.close()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_frame(self):
|
|
||||||
return self.gen.gi_frame
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_running(self):
|
|
||||||
return self.gen.gi_running
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_code(self):
|
|
||||||
return self.gen.gi_code
|
|
||||||
|
|
||||||
if compat.PY35:
|
|
||||||
|
|
||||||
def __await__(self):
|
|
||||||
cr_await = getattr(self.gen, 'cr_await', None)
|
|
||||||
if cr_await is not None:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Cannot await on coroutine {!r} while it's "
|
|
||||||
"awaiting for {!r}".format(self.gen, cr_await))
|
|
||||||
return self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_yieldfrom(self):
|
|
||||||
return self.gen.gi_yieldfrom
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_await(self):
|
|
||||||
return self.gen.cr_await
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_running(self):
|
|
||||||
return self.gen.cr_running
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_code(self):
|
|
||||||
return self.gen.cr_code
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_frame(self):
|
|
||||||
return self.gen.cr_frame
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
# Be careful accessing self.gen.frame -- self.gen might not exist.
|
|
||||||
gen = getattr(self, 'gen', None)
|
|
||||||
frame = getattr(gen, 'gi_frame', None)
|
|
||||||
if frame is None:
|
|
||||||
frame = getattr(gen, 'cr_frame', None)
|
|
||||||
if frame is not None and frame.f_lasti == -1:
|
|
||||||
msg = '%r was never yielded from' % self
|
|
||||||
tb = getattr(self, '_source_traceback', ())
|
|
||||||
if tb:
|
|
||||||
tb = ''.join(traceback.format_list(tb))
|
|
||||||
msg += ('\nCoroutine object created at '
|
|
||||||
'(most recent call last):\n')
|
|
||||||
msg += tb.rstrip()
|
|
||||||
logger.error(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def coroutine(func):
|
|
||||||
"""Decorator to mark coroutines.
|
|
||||||
|
|
||||||
If the coroutine is not yielded from before it is destroyed,
|
|
||||||
an error message is logged.
|
|
||||||
"""
|
|
||||||
if _inspect_iscoroutinefunction(func):
|
|
||||||
# In Python 3.5 that's all we need to do for coroutines
|
|
||||||
# defiend with "async def".
|
|
||||||
# Wrapping in CoroWrapper will happen via
|
|
||||||
# 'sys.set_coroutine_wrapper' function.
|
|
||||||
return func
|
|
||||||
|
|
||||||
if inspect.isgeneratorfunction(func):
|
|
||||||
coro = func
|
|
||||||
else:
|
|
||||||
@functools.wraps(func)
|
|
||||||
def coro(*args, **kw):
|
|
||||||
res = func(*args, **kw)
|
|
||||||
if (base_futures.isfuture(res) or inspect.isgenerator(res) or
|
|
||||||
isinstance(res, CoroWrapper)):
|
|
||||||
res = yield from res
|
|
||||||
elif _AwaitableABC is not None:
|
|
||||||
# If 'func' returns an Awaitable (new in 3.5) we
|
|
||||||
# want to run it.
|
|
||||||
try:
|
|
||||||
await_meth = res.__await__
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if isinstance(res, _AwaitableABC):
|
|
||||||
res = yield from await_meth()
|
|
||||||
return res
|
|
||||||
|
|
||||||
if not _DEBUG:
|
|
||||||
if _types_coroutine is None:
|
|
||||||
wrapper = coro
|
|
||||||
else:
|
|
||||||
wrapper = _types_coroutine(coro)
|
|
||||||
else:
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwds):
|
|
||||||
w = CoroWrapper(coro(*args, **kwds), func=func)
|
|
||||||
if w._source_traceback:
|
|
||||||
del w._source_traceback[-1]
|
|
||||||
# Python < 3.5 does not implement __qualname__
|
|
||||||
# on generator objects, so we set it manually.
|
|
||||||
# We use getattr as some callables (such as
|
|
||||||
# functools.partial may lack __qualname__).
|
|
||||||
w.__name__ = getattr(func, '__name__', None)
|
|
||||||
w.__qualname__ = getattr(func, '__qualname__', None)
|
|
||||||
return w
|
|
||||||
|
|
||||||
wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction().
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
# A marker for iscoroutinefunction.
|
# A marker for iscoroutinefunction.
|
||||||
@@ -252,93 +19,91 @@ _is_coroutine = object()
|
|||||||
|
|
||||||
def iscoroutinefunction(func):
|
def iscoroutinefunction(func):
|
||||||
"""Return True if func is a decorated coroutine function."""
|
"""Return True if func is a decorated coroutine function."""
|
||||||
return (getattr(func, '_is_coroutine', None) is _is_coroutine or
|
return (inspect.iscoroutinefunction(func) or
|
||||||
_inspect_iscoroutinefunction(func))
|
getattr(func, '_is_coroutine', None) is _is_coroutine)
|
||||||
|
|
||||||
|
|
||||||
_COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
|
# Prioritize native coroutine check to speed-up
|
||||||
if _CoroutineABC is not None:
|
# asyncio.iscoroutine.
|
||||||
_COROUTINE_TYPES += (_CoroutineABC,)
|
_COROUTINE_TYPES = (types.CoroutineType, collections.abc.Coroutine)
|
||||||
if _types_CoroutineType is not None:
|
_iscoroutine_typecache = set()
|
||||||
# Prioritize native coroutine check to speed-up
|
|
||||||
# asyncio.iscoroutine.
|
|
||||||
_COROUTINE_TYPES = (_types_CoroutineType,) + _COROUTINE_TYPES
|
|
||||||
|
|
||||||
|
|
||||||
def iscoroutine(obj):
|
def iscoroutine(obj):
|
||||||
"""Return True if obj is a coroutine object."""
|
"""Return True if obj is a coroutine object."""
|
||||||
return isinstance(obj, _COROUTINE_TYPES)
|
if type(obj) in _iscoroutine_typecache:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(obj, _COROUTINE_TYPES):
|
||||||
|
# Just in case we don't want to cache more than 100
|
||||||
|
# positive types. That shouldn't ever happen, unless
|
||||||
|
# someone stressing the system on purpose.
|
||||||
|
if len(_iscoroutine_typecache) < 100:
|
||||||
|
_iscoroutine_typecache.add(type(obj))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _format_coroutine(coro):
|
def _format_coroutine(coro):
|
||||||
assert iscoroutine(coro)
|
assert iscoroutine(coro)
|
||||||
|
|
||||||
if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'):
|
def get_name(coro):
|
||||||
# Most likely a built-in type or a Cython coroutine.
|
# Coroutines compiled with Cython sometimes don't have
|
||||||
|
# proper __qualname__ or __name__. While that is a bug
|
||||||
|
# in Cython, asyncio shouldn't crash with an AttributeError
|
||||||
|
# in its __repr__ functions.
|
||||||
|
if hasattr(coro, '__qualname__') and coro.__qualname__:
|
||||||
|
coro_name = coro.__qualname__
|
||||||
|
elif hasattr(coro, '__name__') and coro.__name__:
|
||||||
|
coro_name = coro.__name__
|
||||||
|
else:
|
||||||
|
# Stop masking Cython bugs, expose them in a friendly way.
|
||||||
|
coro_name = f'<{type(coro).__name__} without __name__>'
|
||||||
|
return f'{coro_name}()'
|
||||||
|
|
||||||
# Built-in types might not have __qualname__ or __name__.
|
def is_running(coro):
|
||||||
coro_name = getattr(
|
|
||||||
coro, '__qualname__',
|
|
||||||
getattr(coro, '__name__', type(coro).__name__))
|
|
||||||
coro_name = '{}()'.format(coro_name)
|
|
||||||
|
|
||||||
running = False
|
|
||||||
try:
|
try:
|
||||||
running = coro.cr_running
|
return coro.cr_running
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
try:
|
try:
|
||||||
running = coro.gi_running
|
return coro.gi_running
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
return False
|
||||||
|
|
||||||
if running:
|
coro_code = None
|
||||||
return '{} running'.format(coro_name)
|
if hasattr(coro, 'cr_code') and coro.cr_code:
|
||||||
|
coro_code = coro.cr_code
|
||||||
|
elif hasattr(coro, 'gi_code') and coro.gi_code:
|
||||||
|
coro_code = coro.gi_code
|
||||||
|
|
||||||
|
coro_name = get_name(coro)
|
||||||
|
|
||||||
|
if not coro_code:
|
||||||
|
# Built-in types might not have __qualname__ or __name__.
|
||||||
|
if is_running(coro):
|
||||||
|
return f'{coro_name} running'
|
||||||
else:
|
else:
|
||||||
return coro_name
|
return coro_name
|
||||||
|
|
||||||
coro_name = None
|
coro_frame = None
|
||||||
if isinstance(coro, CoroWrapper):
|
if hasattr(coro, 'gi_frame') and coro.gi_frame:
|
||||||
func = coro.func
|
|
||||||
coro_name = coro.__qualname__
|
|
||||||
if coro_name is not None:
|
|
||||||
coro_name = '{}()'.format(coro_name)
|
|
||||||
else:
|
|
||||||
func = coro
|
|
||||||
|
|
||||||
if coro_name is None:
|
|
||||||
coro_name = events._format_callback(func, (), {})
|
|
||||||
|
|
||||||
try:
|
|
||||||
coro_code = coro.gi_code
|
|
||||||
except AttributeError:
|
|
||||||
coro_code = coro.cr_code
|
|
||||||
|
|
||||||
try:
|
|
||||||
coro_frame = coro.gi_frame
|
coro_frame = coro.gi_frame
|
||||||
except AttributeError:
|
elif hasattr(coro, 'cr_frame') and coro.cr_frame:
|
||||||
coro_frame = coro.cr_frame
|
coro_frame = coro.cr_frame
|
||||||
|
|
||||||
filename = coro_code.co_filename
|
# If Cython's coroutine has a fake code object without proper
|
||||||
|
# co_filename -- expose that.
|
||||||
|
filename = coro_code.co_filename or '<empty co_filename>'
|
||||||
|
|
||||||
lineno = 0
|
lineno = 0
|
||||||
if (isinstance(coro, CoroWrapper) and
|
|
||||||
not inspect.isgeneratorfunction(coro.func) and
|
if coro_frame is not None:
|
||||||
coro.func is not None):
|
|
||||||
source = events._get_function_source(coro.func)
|
|
||||||
if source is not None:
|
|
||||||
filename, lineno = source
|
|
||||||
if coro_frame is None:
|
|
||||||
coro_repr = ('%s done, defined at %s:%s'
|
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
else:
|
|
||||||
coro_repr = ('%s running, defined at %s:%s'
|
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
elif coro_frame is not None:
|
|
||||||
lineno = coro_frame.f_lineno
|
lineno = coro_frame.f_lineno
|
||||||
coro_repr = ('%s running at %s:%s'
|
coro_repr = f'{coro_name} running at {filename}:{lineno}'
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
else:
|
else:
|
||||||
lineno = coro_code.co_firstlineno
|
lineno = coro_code.co_firstlineno
|
||||||
coro_repr = ('%s done, defined at %s:%s'
|
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
|
|
||||||
return coro_repr
|
return coro_repr
|
||||||
|
|||||||
469
Lib/asyncio/events.py
vendored
469
Lib/asyncio/events.py
vendored
@@ -1,96 +1,50 @@
|
|||||||
"""Event loop and event loop policy."""
|
"""Event loop and event loop policy."""
|
||||||
|
|
||||||
__all__ = ['AbstractEventLoopPolicy',
|
# Contains code from https://github.com/MagicStack/uvloop/tree/v0.16.0
|
||||||
'AbstractEventLoop', 'AbstractServer',
|
# SPDX-License-Identifier: PSF-2.0 AND (MIT OR Apache-2.0)
|
||||||
'Handle', 'TimerHandle',
|
# SPDX-FileCopyrightText: Copyright (c) 2015-2021 MagicStack Inc. http://magic.io
|
||||||
'get_event_loop_policy', 'set_event_loop_policy',
|
|
||||||
'get_event_loop', 'set_event_loop', 'new_event_loop',
|
|
||||||
'get_child_watcher', 'set_child_watcher',
|
|
||||||
'_set_running_loop', 'get_running_loop',
|
|
||||||
'_get_running_loop',
|
|
||||||
]
|
|
||||||
|
|
||||||
import functools
|
__all__ = (
|
||||||
import inspect
|
'AbstractEventLoopPolicy',
|
||||||
import reprlib
|
'AbstractEventLoop', 'AbstractServer',
|
||||||
|
'Handle', 'TimerHandle',
|
||||||
|
'get_event_loop_policy', 'set_event_loop_policy',
|
||||||
|
'get_event_loop', 'set_event_loop', 'new_event_loop',
|
||||||
|
'get_child_watcher', 'set_child_watcher',
|
||||||
|
'_set_running_loop', 'get_running_loop',
|
||||||
|
'_get_running_loop',
|
||||||
|
)
|
||||||
|
|
||||||
|
import contextvars
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
|
||||||
|
|
||||||
from asyncio import compat
|
from . import format_helpers
|
||||||
|
|
||||||
|
|
||||||
def _get_function_source(func):
|
|
||||||
if compat.PY34:
|
|
||||||
func = inspect.unwrap(func)
|
|
||||||
elif hasattr(func, '__wrapped__'):
|
|
||||||
func = func.__wrapped__
|
|
||||||
if inspect.isfunction(func):
|
|
||||||
code = func.__code__
|
|
||||||
return (code.co_filename, code.co_firstlineno)
|
|
||||||
if isinstance(func, functools.partial):
|
|
||||||
return _get_function_source(func.func)
|
|
||||||
if compat.PY34 and isinstance(func, functools.partialmethod):
|
|
||||||
return _get_function_source(func.func)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _format_args_and_kwargs(args, kwargs):
|
|
||||||
"""Format function arguments and keyword arguments.
|
|
||||||
|
|
||||||
Special case for a single parameter: ('hello',) is formatted as ('hello').
|
|
||||||
"""
|
|
||||||
# use reprlib to limit the length of the output
|
|
||||||
items = []
|
|
||||||
if args:
|
|
||||||
items.extend(reprlib.repr(arg) for arg in args)
|
|
||||||
if kwargs:
|
|
||||||
items.extend('{}={}'.format(k, reprlib.repr(v))
|
|
||||||
for k, v in kwargs.items())
|
|
||||||
return '(' + ', '.join(items) + ')'
|
|
||||||
|
|
||||||
|
|
||||||
def _format_callback(func, args, kwargs, suffix=''):
|
|
||||||
if isinstance(func, functools.partial):
|
|
||||||
suffix = _format_args_and_kwargs(args, kwargs) + suffix
|
|
||||||
return _format_callback(func.func, func.args, func.keywords, suffix)
|
|
||||||
|
|
||||||
if hasattr(func, '__qualname__'):
|
|
||||||
func_repr = getattr(func, '__qualname__')
|
|
||||||
elif hasattr(func, '__name__'):
|
|
||||||
func_repr = getattr(func, '__name__')
|
|
||||||
else:
|
|
||||||
func_repr = repr(func)
|
|
||||||
|
|
||||||
func_repr += _format_args_and_kwargs(args, kwargs)
|
|
||||||
if suffix:
|
|
||||||
func_repr += suffix
|
|
||||||
return func_repr
|
|
||||||
|
|
||||||
def _format_callback_source(func, args):
|
|
||||||
func_repr = _format_callback(func, args, None)
|
|
||||||
source = _get_function_source(func)
|
|
||||||
if source:
|
|
||||||
func_repr += ' at %s:%s' % source
|
|
||||||
return func_repr
|
|
||||||
|
|
||||||
|
|
||||||
class Handle:
|
class Handle:
|
||||||
"""Object returned by callback registration methods."""
|
"""Object returned by callback registration methods."""
|
||||||
|
|
||||||
__slots__ = ('_callback', '_args', '_cancelled', '_loop',
|
__slots__ = ('_callback', '_args', '_cancelled', '_loop',
|
||||||
'_source_traceback', '_repr', '__weakref__')
|
'_source_traceback', '_repr', '__weakref__',
|
||||||
|
'_context')
|
||||||
|
|
||||||
def __init__(self, callback, args, loop):
|
def __init__(self, callback, args, loop, context=None):
|
||||||
|
if context is None:
|
||||||
|
context = contextvars.copy_context()
|
||||||
|
self._context = context
|
||||||
self._loop = loop
|
self._loop = loop
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
self._args = args
|
self._args = args
|
||||||
self._cancelled = False
|
self._cancelled = False
|
||||||
self._repr = None
|
self._repr = None
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
self._source_traceback = format_helpers.extract_stack(
|
||||||
|
sys._getframe(1))
|
||||||
else:
|
else:
|
||||||
self._source_traceback = None
|
self._source_traceback = None
|
||||||
|
|
||||||
@@ -99,17 +53,21 @@ class Handle:
|
|||||||
if self._cancelled:
|
if self._cancelled:
|
||||||
info.append('cancelled')
|
info.append('cancelled')
|
||||||
if self._callback is not None:
|
if self._callback is not None:
|
||||||
info.append(_format_callback_source(self._callback, self._args))
|
info.append(format_helpers._format_callback_source(
|
||||||
|
self._callback, self._args))
|
||||||
if self._source_traceback:
|
if self._source_traceback:
|
||||||
frame = self._source_traceback[-1]
|
frame = self._source_traceback[-1]
|
||||||
info.append('created at %s:%s' % (frame[0], frame[1]))
|
info.append(f'created at {frame[0]}:{frame[1]}')
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self._repr is not None:
|
if self._repr is not None:
|
||||||
return self._repr
|
return self._repr
|
||||||
info = self._repr_info()
|
info = self._repr_info()
|
||||||
return '<%s>' % ' '.join(info)
|
return '<{}>'.format(' '.join(info))
|
||||||
|
|
||||||
|
def get_context(self):
|
||||||
|
return self._context
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
if not self._cancelled:
|
if not self._cancelled:
|
||||||
@@ -122,12 +80,18 @@ class Handle:
|
|||||||
self._callback = None
|
self._callback = None
|
||||||
self._args = None
|
self._args = None
|
||||||
|
|
||||||
|
def cancelled(self):
|
||||||
|
return self._cancelled
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
try:
|
try:
|
||||||
self._callback(*self._args)
|
self._context.run(self._callback, *self._args)
|
||||||
except Exception as exc:
|
except (SystemExit, KeyboardInterrupt):
|
||||||
cb = _format_callback_source(self._callback, self._args)
|
raise
|
||||||
msg = 'Exception in callback {}'.format(cb)
|
except BaseException as exc:
|
||||||
|
cb = format_helpers._format_callback_source(
|
||||||
|
self._callback, self._args)
|
||||||
|
msg = f'Exception in callback {cb}'
|
||||||
context = {
|
context = {
|
||||||
'message': msg,
|
'message': msg,
|
||||||
'exception': exc,
|
'exception': exc,
|
||||||
@@ -144,9 +108,8 @@ class TimerHandle(Handle):
|
|||||||
|
|
||||||
__slots__ = ['_scheduled', '_when']
|
__slots__ = ['_scheduled', '_when']
|
||||||
|
|
||||||
def __init__(self, when, callback, args, loop):
|
def __init__(self, when, callback, args, loop, context=None):
|
||||||
assert when is not None
|
super().__init__(callback, args, loop, context)
|
||||||
super().__init__(callback, args, loop)
|
|
||||||
if self._source_traceback:
|
if self._source_traceback:
|
||||||
del self._source_traceback[-1]
|
del self._source_traceback[-1]
|
||||||
self._when = when
|
self._when = when
|
||||||
@@ -155,27 +118,31 @@ class TimerHandle(Handle):
|
|||||||
def _repr_info(self):
|
def _repr_info(self):
|
||||||
info = super()._repr_info()
|
info = super()._repr_info()
|
||||||
pos = 2 if self._cancelled else 1
|
pos = 2 if self._cancelled else 1
|
||||||
info.insert(pos, 'when=%s' % self._when)
|
info.insert(pos, f'when={self._when}')
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self._when)
|
return hash(self._when)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self._when < other._when
|
if isinstance(other, TimerHandle):
|
||||||
|
return self._when < other._when
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
if self._when < other._when:
|
if isinstance(other, TimerHandle):
|
||||||
return True
|
return self._when < other._when or self.__eq__(other)
|
||||||
return self.__eq__(other)
|
return NotImplemented
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other):
|
||||||
return self._when > other._when
|
if isinstance(other, TimerHandle):
|
||||||
|
return self._when > other._when
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other):
|
||||||
if self._when > other._when:
|
if isinstance(other, TimerHandle):
|
||||||
return True
|
return self._when > other._when or self.__eq__(other)
|
||||||
return self.__eq__(other)
|
return NotImplemented
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, TimerHandle):
|
if isinstance(other, TimerHandle):
|
||||||
@@ -185,26 +152,60 @@ class TimerHandle(Handle):
|
|||||||
self._cancelled == other._cancelled)
|
self._cancelled == other._cancelled)
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
equal = self.__eq__(other)
|
|
||||||
return NotImplemented if equal is NotImplemented else not equal
|
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
if not self._cancelled:
|
if not self._cancelled:
|
||||||
self._loop._timer_handle_cancelled(self)
|
self._loop._timer_handle_cancelled(self)
|
||||||
super().cancel()
|
super().cancel()
|
||||||
|
|
||||||
|
def when(self):
|
||||||
|
"""Return a scheduled callback time.
|
||||||
|
|
||||||
|
The time is an absolute timestamp, using the same time
|
||||||
|
reference as loop.time().
|
||||||
|
"""
|
||||||
|
return self._when
|
||||||
|
|
||||||
|
|
||||||
class AbstractServer:
|
class AbstractServer:
|
||||||
"""Abstract server returned by create_server()."""
|
"""Abstract server returned by create_server()."""
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Stop serving. This leaves existing connections open."""
|
"""Stop serving. This leaves existing connections open."""
|
||||||
return NotImplemented
|
raise NotImplementedError
|
||||||
|
|
||||||
def wait_closed(self):
|
def get_loop(self):
|
||||||
|
"""Get the event loop the Server object is attached to."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_serving(self):
|
||||||
|
"""Return True if the server is accepting connections."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def start_serving(self):
|
||||||
|
"""Start accepting connections.
|
||||||
|
|
||||||
|
This method is idempotent, so it can be called when
|
||||||
|
the server is already being serving.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def serve_forever(self):
|
||||||
|
"""Start accepting connections until the coroutine is cancelled.
|
||||||
|
|
||||||
|
The server is closed when the coroutine is cancelled.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def wait_closed(self):
|
||||||
"""Coroutine to wait until service is closed."""
|
"""Coroutine to wait until service is closed."""
|
||||||
return NotImplemented
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *exc):
|
||||||
|
self.close()
|
||||||
|
await self.wait_closed()
|
||||||
|
|
||||||
|
|
||||||
class AbstractEventLoop:
|
class AbstractEventLoop:
|
||||||
@@ -250,23 +251,27 @@ class AbstractEventLoop:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def shutdown_asyncgens(self):
|
async def shutdown_asyncgens(self):
|
||||||
"""Shutdown all active asynchronous generators."""
|
"""Shutdown all active asynchronous generators."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def shutdown_default_executor(self):
|
||||||
|
"""Schedule the shutdown of the default executor."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
# Methods scheduling callbacks. All these return Handles.
|
# Methods scheduling callbacks. All these return Handles.
|
||||||
|
|
||||||
def _timer_handle_cancelled(self, handle):
|
def _timer_handle_cancelled(self, handle):
|
||||||
"""Notification that a TimerHandle has been cancelled."""
|
"""Notification that a TimerHandle has been cancelled."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def call_soon(self, callback, *args):
|
def call_soon(self, callback, *args, context=None):
|
||||||
return self.call_later(0, callback, *args)
|
return self.call_later(0, callback, *args, context=context)
|
||||||
|
|
||||||
def call_later(self, delay, callback, *args):
|
def call_later(self, delay, callback, *args, context=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def call_at(self, when, callback, *args):
|
def call_at(self, when, callback, *args, context=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def time(self):
|
def time(self):
|
||||||
@@ -277,12 +282,12 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
# Method scheduling a coroutine object: create a task.
|
# Method scheduling a coroutine object: create a task.
|
||||||
|
|
||||||
def create_task(self, coro):
|
def create_task(self, coro, *, name=None, context=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
# Methods for interacting with threads.
|
# Methods for interacting with threads.
|
||||||
|
|
||||||
def call_soon_threadsafe(self, callback, *args):
|
def call_soon_threadsafe(self, callback, *args, context=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def run_in_executor(self, executor, func, *args):
|
def run_in_executor(self, executor, func, *args):
|
||||||
@@ -293,21 +298,31 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
# Network I/O methods returning Futures.
|
# Network I/O methods returning Futures.
|
||||||
|
|
||||||
def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
|
async def getaddrinfo(self, host, port, *,
|
||||||
|
family=0, type=0, proto=0, flags=0):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def getnameinfo(self, sockaddr, flags=0):
|
async def getnameinfo(self, sockaddr, flags=0):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def create_connection(self, protocol_factory, host=None, port=None, *,
|
async def create_connection(
|
||||||
ssl=None, family=0, proto=0, flags=0, sock=None,
|
self, protocol_factory, host=None, port=None,
|
||||||
local_addr=None, server_hostname=None):
|
*, ssl=None, family=0, proto=0,
|
||||||
|
flags=0, sock=None, local_addr=None,
|
||||||
|
server_hostname=None,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None,
|
||||||
|
happy_eyeballs_delay=None, interleave=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def create_server(self, protocol_factory, host=None, port=None, *,
|
async def create_server(
|
||||||
family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE,
|
self, protocol_factory, host=None, port=None,
|
||||||
sock=None, backlog=100, ssl=None, reuse_address=None,
|
*, family=socket.AF_UNSPEC,
|
||||||
reuse_port=None):
|
flags=socket.AI_PASSIVE, sock=None, backlog=100,
|
||||||
|
ssl=None, reuse_address=None, reuse_port=None,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None,
|
||||||
|
start_serving=True):
|
||||||
"""A coroutine which creates a TCP server bound to host and port.
|
"""A coroutine which creates a TCP server bound to host and port.
|
||||||
|
|
||||||
The return value is a Server object which can be used to stop
|
The return value is a Server object which can be used to stop
|
||||||
@@ -315,8 +330,8 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
If host is an empty string or None all interfaces are assumed
|
If host is an empty string or None all interfaces are assumed
|
||||||
and a list of multiple sockets will be returned (most likely
|
and a list of multiple sockets will be returned (most likely
|
||||||
one for IPv4 and another one for IPv6). The host parameter can also be a
|
one for IPv4 and another one for IPv6). The host parameter can also be
|
||||||
sequence (e.g. list) of hosts to bind to.
|
a sequence (e.g. list) of hosts to bind to.
|
||||||
|
|
||||||
family can be set to either AF_INET or AF_INET6 to force the
|
family can be set to either AF_INET or AF_INET6 to force the
|
||||||
socket to use IPv4 or IPv6. If not set it will be determined
|
socket to use IPv4 or IPv6. If not set it will be determined
|
||||||
@@ -342,22 +357,62 @@ class AbstractEventLoop:
|
|||||||
the same port as other existing endpoints are bound to, so long as
|
the same port as other existing endpoints are bound to, so long as
|
||||||
they all set this flag when being created. This option is not
|
they all set this flag when being created. This option is not
|
||||||
supported on Windows.
|
supported on Windows.
|
||||||
|
|
||||||
|
ssl_handshake_timeout is the time in seconds that an SSL server
|
||||||
|
will wait for completion of the SSL handshake before aborting the
|
||||||
|
connection. Default is 60s.
|
||||||
|
|
||||||
|
ssl_shutdown_timeout is the time in seconds that an SSL server
|
||||||
|
will wait for completion of the SSL shutdown procedure
|
||||||
|
before aborting the connection. Default is 30s.
|
||||||
|
|
||||||
|
start_serving set to True (default) causes the created server
|
||||||
|
to start accepting connections immediately. When set to False,
|
||||||
|
the user should await Server.start_serving() or Server.serve_forever()
|
||||||
|
to make the server to start accepting connections.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def create_unix_connection(self, protocol_factory, path, *,
|
async def sendfile(self, transport, file, offset=0, count=None,
|
||||||
ssl=None, sock=None,
|
*, fallback=True):
|
||||||
server_hostname=None):
|
"""Send a file through a transport.
|
||||||
|
|
||||||
|
Return an amount of sent bytes.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def create_unix_server(self, protocol_factory, path, *,
|
async def start_tls(self, transport, protocol, sslcontext, *,
|
||||||
sock=None, backlog=100, ssl=None):
|
server_side=False,
|
||||||
|
server_hostname=None,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None):
|
||||||
|
"""Upgrade a transport to TLS.
|
||||||
|
|
||||||
|
Return a new transport that *protocol* should start using
|
||||||
|
immediately.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def create_unix_connection(
|
||||||
|
self, protocol_factory, path=None, *,
|
||||||
|
ssl=None, sock=None,
|
||||||
|
server_hostname=None,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def create_unix_server(
|
||||||
|
self, protocol_factory, path=None, *,
|
||||||
|
sock=None, backlog=100, ssl=None,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None,
|
||||||
|
start_serving=True):
|
||||||
"""A coroutine which creates a UNIX Domain Socket server.
|
"""A coroutine which creates a UNIX Domain Socket server.
|
||||||
|
|
||||||
The return value is a Server object, which can be used to stop
|
The return value is a Server object, which can be used to stop
|
||||||
the service.
|
the service.
|
||||||
|
|
||||||
path is a str, representing a file systsem path to bind the
|
path is a str, representing a file system path to bind the
|
||||||
server socket to.
|
server socket to.
|
||||||
|
|
||||||
sock can optionally be specified in order to use a preexisting
|
sock can optionally be specified in order to use a preexisting
|
||||||
@@ -368,14 +423,40 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
ssl can be set to an SSLContext to enable SSL over the
|
ssl can be set to an SSLContext to enable SSL over the
|
||||||
accepted connections.
|
accepted connections.
|
||||||
|
|
||||||
|
ssl_handshake_timeout is the time in seconds that an SSL server
|
||||||
|
will wait for the SSL handshake to complete (defaults to 60s).
|
||||||
|
|
||||||
|
ssl_shutdown_timeout is the time in seconds that an SSL server
|
||||||
|
will wait for the SSL shutdown to finish (defaults to 30s).
|
||||||
|
|
||||||
|
start_serving set to True (default) causes the created server
|
||||||
|
to start accepting connections immediately. When set to False,
|
||||||
|
the user should await Server.start_serving() or Server.serve_forever()
|
||||||
|
to make the server to start accepting connections.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def create_datagram_endpoint(self, protocol_factory,
|
async def connect_accepted_socket(
|
||||||
local_addr=None, remote_addr=None, *,
|
self, protocol_factory, sock,
|
||||||
family=0, proto=0, flags=0,
|
*, ssl=None,
|
||||||
reuse_address=None, reuse_port=None,
|
ssl_handshake_timeout=None,
|
||||||
allow_broadcast=None, sock=None):
|
ssl_shutdown_timeout=None):
|
||||||
|
"""Handle an accepted connection.
|
||||||
|
|
||||||
|
This is used by servers that accept connections outside of
|
||||||
|
asyncio, but use asyncio to handle connections.
|
||||||
|
|
||||||
|
This method is a coroutine. When completed, the coroutine
|
||||||
|
returns a (transport, protocol) pair.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def create_datagram_endpoint(self, protocol_factory,
|
||||||
|
local_addr=None, remote_addr=None, *,
|
||||||
|
family=0, proto=0, flags=0,
|
||||||
|
reuse_address=None, reuse_port=None,
|
||||||
|
allow_broadcast=None, sock=None):
|
||||||
"""A coroutine which creates a datagram endpoint.
|
"""A coroutine which creates a datagram endpoint.
|
||||||
|
|
||||||
This method will try to establish the endpoint in the background.
|
This method will try to establish the endpoint in the background.
|
||||||
@@ -383,8 +464,8 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
protocol_factory must be a callable returning a protocol instance.
|
protocol_factory must be a callable returning a protocol instance.
|
||||||
|
|
||||||
socket family AF_INET or socket.AF_INET6 depending on host (or
|
socket family AF_INET, socket.AF_INET6 or socket.AF_UNIX depending on
|
||||||
family if specified), socket type SOCK_DGRAM.
|
host (or family if specified), socket type SOCK_DGRAM.
|
||||||
|
|
||||||
reuse_address tells the kernel to reuse a local socket in
|
reuse_address tells the kernel to reuse a local socket in
|
||||||
TIME_WAIT state, without waiting for its natural timeout to
|
TIME_WAIT state, without waiting for its natural timeout to
|
||||||
@@ -408,7 +489,7 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
# Pipes and subprocesses.
|
# Pipes and subprocesses.
|
||||||
|
|
||||||
def connect_read_pipe(self, protocol_factory, pipe):
|
async def connect_read_pipe(self, protocol_factory, pipe):
|
||||||
"""Register read pipe in event loop. Set the pipe to non-blocking mode.
|
"""Register read pipe in event loop. Set the pipe to non-blocking mode.
|
||||||
|
|
||||||
protocol_factory should instantiate object with Protocol interface.
|
protocol_factory should instantiate object with Protocol interface.
|
||||||
@@ -418,10 +499,10 @@ class AbstractEventLoop:
|
|||||||
# The reason to accept file-like object instead of just file descriptor
|
# The reason to accept file-like object instead of just file descriptor
|
||||||
# is: we need to own pipe and close it at transport finishing
|
# is: we need to own pipe and close it at transport finishing
|
||||||
# Can got complicated errors if pass f.fileno(),
|
# Can got complicated errors if pass f.fileno(),
|
||||||
# close fd in pipe transport then close f and vise versa.
|
# close fd in pipe transport then close f and vice versa.
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def connect_write_pipe(self, protocol_factory, pipe):
|
async def connect_write_pipe(self, protocol_factory, pipe):
|
||||||
"""Register write pipe in event loop.
|
"""Register write pipe in event loop.
|
||||||
|
|
||||||
protocol_factory should instantiate object with BaseProtocol interface.
|
protocol_factory should instantiate object with BaseProtocol interface.
|
||||||
@@ -431,17 +512,21 @@ class AbstractEventLoop:
|
|||||||
# The reason to accept file-like object instead of just file descriptor
|
# The reason to accept file-like object instead of just file descriptor
|
||||||
# is: we need to own pipe and close it at transport finishing
|
# is: we need to own pipe and close it at transport finishing
|
||||||
# Can got complicated errors if pass f.fileno(),
|
# Can got complicated errors if pass f.fileno(),
|
||||||
# close fd in pipe transport then close f and vise versa.
|
# close fd in pipe transport then close f and vice versa.
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
|
async def subprocess_shell(self, protocol_factory, cmd, *,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
**kwargs):
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
**kwargs):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE,
|
async def subprocess_exec(self, protocol_factory, *args,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
**kwargs):
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
**kwargs):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
# Ready-based callback registration methods.
|
# Ready-based callback registration methods.
|
||||||
@@ -463,16 +548,32 @@ class AbstractEventLoop:
|
|||||||
|
|
||||||
# Completion based I/O methods returning Futures.
|
# Completion based I/O methods returning Futures.
|
||||||
|
|
||||||
def sock_recv(self, sock, nbytes):
|
async def sock_recv(self, sock, nbytes):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def sock_sendall(self, sock, data):
|
async def sock_recv_into(self, sock, buf):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def sock_connect(self, sock, address):
|
async def sock_recvfrom(self, sock, bufsize):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def sock_accept(self, sock):
|
async def sock_recvfrom_into(self, sock, buf, nbytes=0):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def sock_sendall(self, sock, data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def sock_sendto(self, sock, data, address):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def sock_connect(self, sock, address):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def sock_accept(self, sock):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
async def sock_sendfile(self, sock, file, offset=0, count=None,
|
||||||
|
*, fallback=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
# Signal handling.
|
# Signal handling.
|
||||||
@@ -520,7 +621,7 @@ class AbstractEventLoopPolicy:
|
|||||||
def get_event_loop(self):
|
def get_event_loop(self):
|
||||||
"""Get the event loop for the current context.
|
"""Get the event loop for the current context.
|
||||||
|
|
||||||
Returns an event loop object implementing the BaseEventLoop interface,
|
Returns an event loop object implementing the AbstractEventLoop interface,
|
||||||
or raises an exception in case no event loop has been set for the
|
or raises an exception in case no event loop has been set for the
|
||||||
current context and the current policy does not specify to create one.
|
current context and the current policy does not specify to create one.
|
||||||
|
|
||||||
@@ -571,23 +672,43 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
|
|||||||
self._local = self._Local()
|
self._local = self._Local()
|
||||||
|
|
||||||
def get_event_loop(self):
|
def get_event_loop(self):
|
||||||
"""Get the event loop.
|
"""Get the event loop for the current context.
|
||||||
|
|
||||||
This may be None or an instance of EventLoop.
|
Returns an instance of EventLoop or raises an exception.
|
||||||
"""
|
"""
|
||||||
if (self._local._loop is None and
|
if (self._local._loop is None and
|
||||||
not self._local._set_called and
|
not self._local._set_called and
|
||||||
isinstance(threading.current_thread(), threading._MainThread)):
|
threading.current_thread() is threading.main_thread()):
|
||||||
|
stacklevel = 2
|
||||||
|
try:
|
||||||
|
f = sys._getframe(1)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Move up the call stack so that the warning is attached
|
||||||
|
# to the line outside asyncio itself.
|
||||||
|
while f:
|
||||||
|
module = f.f_globals.get('__name__')
|
||||||
|
if not (module == 'asyncio' or module.startswith('asyncio.')):
|
||||||
|
break
|
||||||
|
f = f.f_back
|
||||||
|
stacklevel += 1
|
||||||
|
import warnings
|
||||||
|
warnings.warn('There is no current event loop',
|
||||||
|
DeprecationWarning, stacklevel=stacklevel)
|
||||||
self.set_event_loop(self.new_event_loop())
|
self.set_event_loop(self.new_event_loop())
|
||||||
|
|
||||||
if self._local._loop is None:
|
if self._local._loop is None:
|
||||||
raise RuntimeError('There is no current event loop in thread %r.'
|
raise RuntimeError('There is no current event loop in thread %r.'
|
||||||
% threading.current_thread().name)
|
% threading.current_thread().name)
|
||||||
|
|
||||||
return self._local._loop
|
return self._local._loop
|
||||||
|
|
||||||
def set_event_loop(self, loop):
|
def set_event_loop(self, loop):
|
||||||
"""Set the event loop."""
|
"""Set the event loop."""
|
||||||
self._local._set_called = True
|
self._local._set_called = True
|
||||||
assert loop is None or isinstance(loop, AbstractEventLoop)
|
if loop is not None and not isinstance(loop, AbstractEventLoop):
|
||||||
|
raise TypeError(f"loop must be an instance of AbstractEventLoop or None, not '{type(loop).__name__}'")
|
||||||
self._local._loop = loop
|
self._local._loop = loop
|
||||||
|
|
||||||
def new_event_loop(self):
|
def new_event_loop(self):
|
||||||
@@ -611,7 +732,9 @@ _lock = threading.Lock()
|
|||||||
|
|
||||||
# A TLS for the running event loop, used by _get_running_loop.
|
# A TLS for the running event loop, used by _get_running_loop.
|
||||||
class _RunningLoop(threading.local):
|
class _RunningLoop(threading.local):
|
||||||
_loop = None
|
loop_pid = (None, None)
|
||||||
|
|
||||||
|
|
||||||
_running_loop = _RunningLoop()
|
_running_loop = _RunningLoop()
|
||||||
|
|
||||||
|
|
||||||
@@ -633,7 +756,10 @@ def _get_running_loop():
|
|||||||
This is a low-level function intended to be used by event loops.
|
This is a low-level function intended to be used by event loops.
|
||||||
This function is thread-specific.
|
This function is thread-specific.
|
||||||
"""
|
"""
|
||||||
return _running_loop._loop
|
# NOTE: this function is implemented in C (see _asynciomodule.c)
|
||||||
|
running_loop, pid = _running_loop.loop_pid
|
||||||
|
if running_loop is not None and pid == os.getpid():
|
||||||
|
return running_loop
|
||||||
|
|
||||||
|
|
||||||
def _set_running_loop(loop):
|
def _set_running_loop(loop):
|
||||||
@@ -642,7 +768,8 @@ def _set_running_loop(loop):
|
|||||||
This is a low-level function intended to be used by event loops.
|
This is a low-level function intended to be used by event loops.
|
||||||
This function is thread-specific.
|
This function is thread-specific.
|
||||||
"""
|
"""
|
||||||
_running_loop._loop = loop
|
# NOTE: this function is implemented in C (see _asynciomodule.c)
|
||||||
|
_running_loop.loop_pid = (loop, os.getpid())
|
||||||
|
|
||||||
|
|
||||||
def _init_event_loop_policy():
|
def _init_event_loop_policy():
|
||||||
@@ -665,7 +792,8 @@ def set_event_loop_policy(policy):
|
|||||||
|
|
||||||
If policy is None, the default policy is restored."""
|
If policy is None, the default policy is restored."""
|
||||||
global _event_loop_policy
|
global _event_loop_policy
|
||||||
assert policy is None or isinstance(policy, AbstractEventLoopPolicy)
|
if policy is not None and not isinstance(policy, AbstractEventLoopPolicy):
|
||||||
|
raise TypeError(f"policy must be an instance of AbstractEventLoopPolicy or None, not '{type(policy).__name__}'")
|
||||||
_event_loop_policy = policy
|
_event_loop_policy = policy
|
||||||
|
|
||||||
|
|
||||||
@@ -678,6 +806,7 @@ def get_event_loop():
|
|||||||
If there is no running event loop set, the function will return
|
If there is no running event loop set, the function will return
|
||||||
the result of `get_event_loop_policy().get_event_loop()` call.
|
the result of `get_event_loop_policy().get_event_loop()` call.
|
||||||
"""
|
"""
|
||||||
|
# NOTE: this function is implemented in C (see _asynciomodule.c)
|
||||||
current_loop = _get_running_loop()
|
current_loop = _get_running_loop()
|
||||||
if current_loop is not None:
|
if current_loop is not None:
|
||||||
return current_loop
|
return current_loop
|
||||||
@@ -703,3 +832,37 @@ def set_child_watcher(watcher):
|
|||||||
"""Equivalent to calling
|
"""Equivalent to calling
|
||||||
get_event_loop_policy().set_child_watcher(watcher)."""
|
get_event_loop_policy().set_child_watcher(watcher)."""
|
||||||
return get_event_loop_policy().set_child_watcher(watcher)
|
return get_event_loop_policy().set_child_watcher(watcher)
|
||||||
|
|
||||||
|
|
||||||
|
# Alias pure-Python implementations for testing purposes.
|
||||||
|
_py__get_running_loop = _get_running_loop
|
||||||
|
_py__set_running_loop = _set_running_loop
|
||||||
|
_py_get_running_loop = get_running_loop
|
||||||
|
_py_get_event_loop = get_event_loop
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
# get_event_loop() is one of the most frequently called
|
||||||
|
# functions in asyncio. Pure Python implementation is
|
||||||
|
# about 4 times slower than C-accelerated.
|
||||||
|
from _asyncio import (_get_running_loop, _set_running_loop,
|
||||||
|
get_running_loop, get_event_loop)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Alias C implementations for testing purposes.
|
||||||
|
_c__get_running_loop = _get_running_loop
|
||||||
|
_c__set_running_loop = _set_running_loop
|
||||||
|
_c_get_running_loop = get_running_loop
|
||||||
|
_c_get_event_loop = get_event_loop
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(os, 'fork'):
|
||||||
|
def on_fork():
|
||||||
|
# Reset the loop and wakeupfd in the forked child process.
|
||||||
|
if _event_loop_policy is not None:
|
||||||
|
_event_loop_policy._local = BaseDefaultEventLoopPolicy._Local()
|
||||||
|
_set_running_loop(None)
|
||||||
|
signal.set_wakeup_fd(-1)
|
||||||
|
|
||||||
|
os.register_at_fork(after_in_child=on_fork)
|
||||||
|
|||||||
62
Lib/asyncio/exceptions.py
vendored
Normal file
62
Lib/asyncio/exceptions.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""asyncio exceptions."""
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('BrokenBarrierError',
|
||||||
|
'CancelledError', 'InvalidStateError', 'TimeoutError',
|
||||||
|
'IncompleteReadError', 'LimitOverrunError',
|
||||||
|
'SendfileNotAvailableError')
|
||||||
|
|
||||||
|
|
||||||
|
class CancelledError(BaseException):
|
||||||
|
"""The Future or Task was cancelled."""
|
||||||
|
|
||||||
|
|
||||||
|
TimeoutError = TimeoutError # make local alias for the standard exception
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidStateError(Exception):
|
||||||
|
"""The operation is not allowed in this state."""
|
||||||
|
|
||||||
|
|
||||||
|
class SendfileNotAvailableError(RuntimeError):
|
||||||
|
"""Sendfile syscall is not available.
|
||||||
|
|
||||||
|
Raised if OS does not support sendfile syscall for given socket or
|
||||||
|
file type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IncompleteReadError(EOFError):
|
||||||
|
"""
|
||||||
|
Incomplete read error. Attributes:
|
||||||
|
|
||||||
|
- partial: read bytes string before the end of stream was reached
|
||||||
|
- expected: total number of expected bytes (or None if unknown)
|
||||||
|
"""
|
||||||
|
def __init__(self, partial, expected):
|
||||||
|
r_expected = 'undefined' if expected is None else repr(expected)
|
||||||
|
super().__init__(f'{len(partial)} bytes read on a total of '
|
||||||
|
f'{r_expected} expected bytes')
|
||||||
|
self.partial = partial
|
||||||
|
self.expected = expected
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return type(self), (self.partial, self.expected)
|
||||||
|
|
||||||
|
|
||||||
|
class LimitOverrunError(Exception):
|
||||||
|
"""Reached the buffer limit while looking for a separator.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
- consumed: total number of to be consumed bytes.
|
||||||
|
"""
|
||||||
|
def __init__(self, message, consumed):
|
||||||
|
super().__init__(message)
|
||||||
|
self.consumed = consumed
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return type(self), (self.args[0], self.consumed)
|
||||||
|
|
||||||
|
|
||||||
|
class BrokenBarrierError(RuntimeError):
|
||||||
|
"""Barrier is broken by barrier.abort() call."""
|
||||||
76
Lib/asyncio/format_helpers.py
vendored
Normal file
76
Lib/asyncio/format_helpers.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import functools
|
||||||
|
import inspect
|
||||||
|
import reprlib
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from . import constants
|
||||||
|
|
||||||
|
|
||||||
|
def _get_function_source(func):
|
||||||
|
func = inspect.unwrap(func)
|
||||||
|
if inspect.isfunction(func):
|
||||||
|
code = func.__code__
|
||||||
|
return (code.co_filename, code.co_firstlineno)
|
||||||
|
if isinstance(func, functools.partial):
|
||||||
|
return _get_function_source(func.func)
|
||||||
|
if isinstance(func, functools.partialmethod):
|
||||||
|
return _get_function_source(func.func)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _format_callback_source(func, args):
|
||||||
|
func_repr = _format_callback(func, args, None)
|
||||||
|
source = _get_function_source(func)
|
||||||
|
if source:
|
||||||
|
func_repr += f' at {source[0]}:{source[1]}'
|
||||||
|
return func_repr
|
||||||
|
|
||||||
|
|
||||||
|
def _format_args_and_kwargs(args, kwargs):
|
||||||
|
"""Format function arguments and keyword arguments.
|
||||||
|
|
||||||
|
Special case for a single parameter: ('hello',) is formatted as ('hello').
|
||||||
|
"""
|
||||||
|
# use reprlib to limit the length of the output
|
||||||
|
items = []
|
||||||
|
if args:
|
||||||
|
items.extend(reprlib.repr(arg) for arg in args)
|
||||||
|
if kwargs:
|
||||||
|
items.extend(f'{k}={reprlib.repr(v)}' for k, v in kwargs.items())
|
||||||
|
return '({})'.format(', '.join(items))
|
||||||
|
|
||||||
|
|
||||||
|
def _format_callback(func, args, kwargs, suffix=''):
|
||||||
|
if isinstance(func, functools.partial):
|
||||||
|
suffix = _format_args_and_kwargs(args, kwargs) + suffix
|
||||||
|
return _format_callback(func.func, func.args, func.keywords, suffix)
|
||||||
|
|
||||||
|
if hasattr(func, '__qualname__') and func.__qualname__:
|
||||||
|
func_repr = func.__qualname__
|
||||||
|
elif hasattr(func, '__name__') and func.__name__:
|
||||||
|
func_repr = func.__name__
|
||||||
|
else:
|
||||||
|
func_repr = repr(func)
|
||||||
|
|
||||||
|
func_repr += _format_args_and_kwargs(args, kwargs)
|
||||||
|
if suffix:
|
||||||
|
func_repr += suffix
|
||||||
|
return func_repr
|
||||||
|
|
||||||
|
|
||||||
|
def extract_stack(f=None, limit=None):
|
||||||
|
"""Replacement for traceback.extract_stack() that only does the
|
||||||
|
necessary work for asyncio debug mode.
|
||||||
|
"""
|
||||||
|
if f is None:
|
||||||
|
f = sys._getframe().f_back
|
||||||
|
if limit is None:
|
||||||
|
# Limit the amount of work to a reasonable amount, as extract_stack()
|
||||||
|
# can be called for each coroutine and future in debug mode.
|
||||||
|
limit = constants.DEBUG_STACK_DEPTH
|
||||||
|
stack = traceback.StackSummary.extract(traceback.walk_stack(f),
|
||||||
|
limit=limit,
|
||||||
|
lookup_lines=False)
|
||||||
|
stack.reverse()
|
||||||
|
return stack
|
||||||
298
Lib/asyncio/futures.py
vendored
298
Lib/asyncio/futures.py
vendored
@@ -1,21 +1,21 @@
|
|||||||
"""A Future class similar to the one in PEP 3148."""
|
"""A Future class similar to the one in PEP 3148."""
|
||||||
|
|
||||||
__all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError',
|
__all__ = (
|
||||||
'Future', 'wrap_future', 'isfuture']
|
'Future', 'wrap_future', 'isfuture',
|
||||||
|
)
|
||||||
|
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
import contextvars
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
from types import GenericAlias
|
||||||
|
|
||||||
from . import base_futures
|
from . import base_futures
|
||||||
from . import compat
|
|
||||||
from . import events
|
from . import events
|
||||||
|
from . import exceptions
|
||||||
|
from . import format_helpers
|
||||||
|
|
||||||
|
|
||||||
CancelledError = base_futures.CancelledError
|
|
||||||
InvalidStateError = base_futures.InvalidStateError
|
|
||||||
TimeoutError = base_futures.TimeoutError
|
|
||||||
isfuture = base_futures.isfuture
|
isfuture = base_futures.isfuture
|
||||||
|
|
||||||
|
|
||||||
@@ -27,96 +27,18 @@ _FINISHED = base_futures._FINISHED
|
|||||||
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
|
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
|
||||||
|
|
||||||
|
|
||||||
class _TracebackLogger:
|
|
||||||
"""Helper to log a traceback upon destruction if not cleared.
|
|
||||||
|
|
||||||
This solves a nasty problem with Futures and Tasks that have an
|
|
||||||
exception set: if nobody asks for the exception, the exception is
|
|
||||||
never logged. This violates the Zen of Python: 'Errors should
|
|
||||||
never pass silently. Unless explicitly silenced.'
|
|
||||||
|
|
||||||
However, we don't want to log the exception as soon as
|
|
||||||
set_exception() is called: if the calling code is written
|
|
||||||
properly, it will get the exception and handle it properly. But
|
|
||||||
we *do* want to log it if result() or exception() was never called
|
|
||||||
-- otherwise developers waste a lot of time wondering why their
|
|
||||||
buggy code fails silently.
|
|
||||||
|
|
||||||
An earlier attempt added a __del__() method to the Future class
|
|
||||||
itself, but this backfired because the presence of __del__()
|
|
||||||
prevents garbage collection from breaking cycles. A way out of
|
|
||||||
this catch-22 is to avoid having a __del__() method on the Future
|
|
||||||
class itself, but instead to have a reference to a helper object
|
|
||||||
with a __del__() method that logs the traceback, where we ensure
|
|
||||||
that the helper object doesn't participate in cycles, and only the
|
|
||||||
Future has a reference to it.
|
|
||||||
|
|
||||||
The helper object is added when set_exception() is called. When
|
|
||||||
the Future is collected, and the helper is present, the helper
|
|
||||||
object is also collected, and its __del__() method will log the
|
|
||||||
traceback. When the Future's result() or exception() method is
|
|
||||||
called (and a helper object is present), it removes the helper
|
|
||||||
object, after calling its clear() method to prevent it from
|
|
||||||
logging.
|
|
||||||
|
|
||||||
One downside is that we do a fair amount of work to extract the
|
|
||||||
traceback from the exception, even when it is never logged. It
|
|
||||||
would seem cheaper to just store the exception object, but that
|
|
||||||
references the traceback, which references stack frames, which may
|
|
||||||
reference the Future, which references the _TracebackLogger, and
|
|
||||||
then the _TracebackLogger would be included in a cycle, which is
|
|
||||||
what we're trying to avoid! As an optimization, we don't
|
|
||||||
immediately format the exception; we only do the work when
|
|
||||||
activate() is called, which call is delayed until after all the
|
|
||||||
Future's callbacks have run. Since usually a Future has at least
|
|
||||||
one callback (typically set by 'yield from') and usually that
|
|
||||||
callback extracts the callback, thereby removing the need to
|
|
||||||
format the exception.
|
|
||||||
|
|
||||||
PS. I don't claim credit for this solution. I first heard of it
|
|
||||||
in a discussion about closing files when they are collected.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ('loop', 'source_traceback', 'exc', 'tb')
|
|
||||||
|
|
||||||
def __init__(self, future, exc):
|
|
||||||
self.loop = future._loop
|
|
||||||
self.source_traceback = future._source_traceback
|
|
||||||
self.exc = exc
|
|
||||||
self.tb = None
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
exc = self.exc
|
|
||||||
if exc is not None:
|
|
||||||
self.exc = None
|
|
||||||
self.tb = traceback.format_exception(exc.__class__, exc,
|
|
||||||
exc.__traceback__)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.exc = None
|
|
||||||
self.tb = None
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.tb:
|
|
||||||
msg = 'Future/Task exception was never retrieved\n'
|
|
||||||
if self.source_traceback:
|
|
||||||
src = ''.join(traceback.format_list(self.source_traceback))
|
|
||||||
msg += 'Future/Task created at (most recent call last):\n'
|
|
||||||
msg += '%s\n' % src.rstrip()
|
|
||||||
msg += ''.join(self.tb).rstrip()
|
|
||||||
self.loop.call_exception_handler({'message': msg})
|
|
||||||
|
|
||||||
|
|
||||||
class Future:
|
class Future:
|
||||||
"""This class is *almost* compatible with concurrent.futures.Future.
|
"""This class is *almost* compatible with concurrent.futures.Future.
|
||||||
|
|
||||||
Differences:
|
Differences:
|
||||||
|
|
||||||
|
- This class is not thread-safe.
|
||||||
|
|
||||||
- result() and exception() do not take a timeout argument and
|
- result() and exception() do not take a timeout argument and
|
||||||
raise an exception when the future isn't done yet.
|
raise an exception when the future isn't done yet.
|
||||||
|
|
||||||
- Callbacks registered with add_done_callback() are always called
|
- Callbacks registered with add_done_callback() are always called
|
||||||
via the event loop's call_soon_threadsafe().
|
via the event loop's call_soon().
|
||||||
|
|
||||||
- This class is not compatible with the wait() and as_completed()
|
- This class is not compatible with the wait() and as_completed()
|
||||||
methods in the concurrent.futures package.
|
methods in the concurrent.futures package.
|
||||||
@@ -130,6 +52,9 @@ class Future:
|
|||||||
_exception = None
|
_exception = None
|
||||||
_loop = None
|
_loop = None
|
||||||
_source_traceback = None
|
_source_traceback = None
|
||||||
|
_cancel_message = None
|
||||||
|
# A saved CancelledError for later chaining as an exception context.
|
||||||
|
_cancelled_exc = None
|
||||||
|
|
||||||
# This field is used for a dual purpose:
|
# This field is used for a dual purpose:
|
||||||
# - Its presence is a marker to declare that a class implements
|
# - Its presence is a marker to declare that a class implements
|
||||||
@@ -137,12 +62,12 @@ class Future:
|
|||||||
# The value must also be not-None, to enable a subclass to declare
|
# The value must also be not-None, to enable a subclass to declare
|
||||||
# that it is not compatible by setting this to None.
|
# that it is not compatible by setting this to None.
|
||||||
# - It is set by __iter__() below so that Task._step() can tell
|
# - It is set by __iter__() below so that Task._step() can tell
|
||||||
# the difference between `yield from Future()` (correct) vs.
|
# the difference between
|
||||||
|
# `await Future()` or`yield from Future()` (correct) vs.
|
||||||
# `yield Future()` (incorrect).
|
# `yield Future()` (incorrect).
|
||||||
_asyncio_future_blocking = False
|
_asyncio_future_blocking = False
|
||||||
|
|
||||||
_log_traceback = False # Used for Python 3.4 and later
|
__log_traceback = False
|
||||||
_tb_logger = None # Used for Python 3.3 only
|
|
||||||
|
|
||||||
def __init__(self, *, loop=None):
|
def __init__(self, *, loop=None):
|
||||||
"""Initialize the future.
|
"""Initialize the future.
|
||||||
@@ -157,50 +82,83 @@ class Future:
|
|||||||
self._loop = loop
|
self._loop = loop
|
||||||
self._callbacks = []
|
self._callbacks = []
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
self._source_traceback = format_helpers.extract_stack(
|
||||||
|
sys._getframe(1))
|
||||||
_repr_info = base_futures._future_repr_info
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info()))
|
return base_futures._future_repr(self)
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
def __del__(self):
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
if not self.__log_traceback:
|
||||||
# to the PEP 442.
|
# set_exception() was not called, or result() or exception()
|
||||||
if compat.PY34:
|
# has consumed the exception
|
||||||
def __del__(self):
|
return
|
||||||
if not self._log_traceback:
|
exc = self._exception
|
||||||
# set_exception() was not called, or result() or exception()
|
context = {
|
||||||
# has consumed the exception
|
'message':
|
||||||
return
|
f'{self.__class__.__name__} exception was never retrieved',
|
||||||
exc = self._exception
|
'exception': exc,
|
||||||
context = {
|
'future': self,
|
||||||
'message': ('%s exception was never retrieved'
|
}
|
||||||
% self.__class__.__name__),
|
if self._source_traceback:
|
||||||
'exception': exc,
|
context['source_traceback'] = self._source_traceback
|
||||||
'future': self,
|
self._loop.call_exception_handler(context)
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
|
|
||||||
def __class_getitem__(cls, type):
|
__class_getitem__ = classmethod(GenericAlias)
|
||||||
return cls
|
|
||||||
|
|
||||||
def cancel(self):
|
@property
|
||||||
|
def _log_traceback(self):
|
||||||
|
return self.__log_traceback
|
||||||
|
|
||||||
|
@_log_traceback.setter
|
||||||
|
def _log_traceback(self, val):
|
||||||
|
if val:
|
||||||
|
raise ValueError('_log_traceback can only be set to False')
|
||||||
|
self.__log_traceback = False
|
||||||
|
|
||||||
|
def get_loop(self):
|
||||||
|
"""Return the event loop the Future is bound to."""
|
||||||
|
loop = self._loop
|
||||||
|
if loop is None:
|
||||||
|
raise RuntimeError("Future object is not initialized.")
|
||||||
|
return loop
|
||||||
|
|
||||||
|
def _make_cancelled_error(self):
|
||||||
|
"""Create the CancelledError to raise if the Future is cancelled.
|
||||||
|
|
||||||
|
This should only be called once when handling a cancellation since
|
||||||
|
it erases the saved context exception value.
|
||||||
|
"""
|
||||||
|
if self._cancelled_exc is not None:
|
||||||
|
exc = self._cancelled_exc
|
||||||
|
self._cancelled_exc = None
|
||||||
|
return exc
|
||||||
|
|
||||||
|
if self._cancel_message is None:
|
||||||
|
exc = exceptions.CancelledError()
|
||||||
|
else:
|
||||||
|
exc = exceptions.CancelledError(self._cancel_message)
|
||||||
|
exc.__context__ = self._cancelled_exc
|
||||||
|
# Remove the reference since we don't need this anymore.
|
||||||
|
self._cancelled_exc = None
|
||||||
|
return exc
|
||||||
|
|
||||||
|
def cancel(self, msg=None):
|
||||||
"""Cancel the future and schedule callbacks.
|
"""Cancel the future and schedule callbacks.
|
||||||
|
|
||||||
If the future is already done or cancelled, return False. Otherwise,
|
If the future is already done or cancelled, return False. Otherwise,
|
||||||
change the future's state to cancelled, schedule the callbacks and
|
change the future's state to cancelled, schedule the callbacks and
|
||||||
return True.
|
return True.
|
||||||
"""
|
"""
|
||||||
|
self.__log_traceback = False
|
||||||
if self._state != _PENDING:
|
if self._state != _PENDING:
|
||||||
return False
|
return False
|
||||||
self._state = _CANCELLED
|
self._state = _CANCELLED
|
||||||
self._schedule_callbacks()
|
self._cancel_message = msg
|
||||||
|
self.__schedule_callbacks()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _schedule_callbacks(self):
|
def __schedule_callbacks(self):
|
||||||
"""Internal: Ask the event loop to call all callbacks.
|
"""Internal: Ask the event loop to call all callbacks.
|
||||||
|
|
||||||
The callbacks are scheduled to be called as soon as possible. Also
|
The callbacks are scheduled to be called as soon as possible. Also
|
||||||
@@ -211,8 +169,8 @@ class Future:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._callbacks[:] = []
|
self._callbacks[:] = []
|
||||||
for callback in callbacks:
|
for callback, ctx in callbacks:
|
||||||
self._loop.call_soon(callback, self)
|
self._loop.call_soon(callback, self, context=ctx)
|
||||||
|
|
||||||
def cancelled(self):
|
def cancelled(self):
|
||||||
"""Return True if the future was cancelled."""
|
"""Return True if the future was cancelled."""
|
||||||
@@ -236,15 +194,13 @@ class Future:
|
|||||||
the future is done and has an exception set, this exception is raised.
|
the future is done and has an exception set, this exception is raised.
|
||||||
"""
|
"""
|
||||||
if self._state == _CANCELLED:
|
if self._state == _CANCELLED:
|
||||||
raise CancelledError
|
exc = self._make_cancelled_error()
|
||||||
|
raise exc
|
||||||
if self._state != _FINISHED:
|
if self._state != _FINISHED:
|
||||||
raise InvalidStateError('Result is not ready.')
|
raise exceptions.InvalidStateError('Result is not ready.')
|
||||||
self._log_traceback = False
|
self.__log_traceback = False
|
||||||
if self._tb_logger is not None:
|
|
||||||
self._tb_logger.clear()
|
|
||||||
self._tb_logger = None
|
|
||||||
if self._exception is not None:
|
if self._exception is not None:
|
||||||
raise self._exception
|
raise self._exception.with_traceback(self._exception_tb)
|
||||||
return self._result
|
return self._result
|
||||||
|
|
||||||
def exception(self):
|
def exception(self):
|
||||||
@@ -256,16 +212,14 @@ class Future:
|
|||||||
InvalidStateError.
|
InvalidStateError.
|
||||||
"""
|
"""
|
||||||
if self._state == _CANCELLED:
|
if self._state == _CANCELLED:
|
||||||
raise CancelledError
|
exc = self._make_cancelled_error()
|
||||||
|
raise exc
|
||||||
if self._state != _FINISHED:
|
if self._state != _FINISHED:
|
||||||
raise InvalidStateError('Exception is not set.')
|
raise exceptions.InvalidStateError('Exception is not set.')
|
||||||
self._log_traceback = False
|
self.__log_traceback = False
|
||||||
if self._tb_logger is not None:
|
|
||||||
self._tb_logger.clear()
|
|
||||||
self._tb_logger = None
|
|
||||||
return self._exception
|
return self._exception
|
||||||
|
|
||||||
def add_done_callback(self, fn):
|
def add_done_callback(self, fn, *, context=None):
|
||||||
"""Add a callback to be run when the future becomes done.
|
"""Add a callback to be run when the future becomes done.
|
||||||
|
|
||||||
The callback is called with a single argument - the future object. If
|
The callback is called with a single argument - the future object. If
|
||||||
@@ -273,9 +227,11 @@ class Future:
|
|||||||
scheduled with call_soon.
|
scheduled with call_soon.
|
||||||
"""
|
"""
|
||||||
if self._state != _PENDING:
|
if self._state != _PENDING:
|
||||||
self._loop.call_soon(fn, self)
|
self._loop.call_soon(fn, self, context=context)
|
||||||
else:
|
else:
|
||||||
self._callbacks.append(fn)
|
if context is None:
|
||||||
|
context = contextvars.copy_context()
|
||||||
|
self._callbacks.append((fn, context))
|
||||||
|
|
||||||
# New method not in PEP 3148.
|
# New method not in PEP 3148.
|
||||||
|
|
||||||
@@ -284,7 +240,9 @@ class Future:
|
|||||||
|
|
||||||
Returns the number of callbacks removed.
|
Returns the number of callbacks removed.
|
||||||
"""
|
"""
|
||||||
filtered_callbacks = [f for f in self._callbacks if f != fn]
|
filtered_callbacks = [(f, ctx)
|
||||||
|
for (f, ctx) in self._callbacks
|
||||||
|
if f != fn]
|
||||||
removed_count = len(self._callbacks) - len(filtered_callbacks)
|
removed_count = len(self._callbacks) - len(filtered_callbacks)
|
||||||
if removed_count:
|
if removed_count:
|
||||||
self._callbacks[:] = filtered_callbacks
|
self._callbacks[:] = filtered_callbacks
|
||||||
@@ -299,10 +257,10 @@ class Future:
|
|||||||
InvalidStateError.
|
InvalidStateError.
|
||||||
"""
|
"""
|
||||||
if self._state != _PENDING:
|
if self._state != _PENDING:
|
||||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
|
||||||
self._result = result
|
self._result = result
|
||||||
self._state = _FINISHED
|
self._state = _FINISHED
|
||||||
self._schedule_callbacks()
|
self.__schedule_callbacks()
|
||||||
|
|
||||||
def set_exception(self, exception):
|
def set_exception(self, exception):
|
||||||
"""Mark the future done and set an exception.
|
"""Mark the future done and set an exception.
|
||||||
@@ -311,38 +269,45 @@ class Future:
|
|||||||
InvalidStateError.
|
InvalidStateError.
|
||||||
"""
|
"""
|
||||||
if self._state != _PENDING:
|
if self._state != _PENDING:
|
||||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
|
||||||
if isinstance(exception, type):
|
if isinstance(exception, type):
|
||||||
exception = exception()
|
exception = exception()
|
||||||
if type(exception) is StopIteration:
|
if type(exception) is StopIteration:
|
||||||
raise TypeError("StopIteration interacts badly with generators "
|
raise TypeError("StopIteration interacts badly with generators "
|
||||||
"and cannot be raised into a Future")
|
"and cannot be raised into a Future")
|
||||||
self._exception = exception
|
self._exception = exception
|
||||||
|
self._exception_tb = exception.__traceback__
|
||||||
self._state = _FINISHED
|
self._state = _FINISHED
|
||||||
self._schedule_callbacks()
|
self.__schedule_callbacks()
|
||||||
if compat.PY34:
|
self.__log_traceback = True
|
||||||
self._log_traceback = True
|
|
||||||
else:
|
|
||||||
self._tb_logger = _TracebackLogger(self, exception)
|
|
||||||
# Arrange for the logger to be activated after all callbacks
|
|
||||||
# have had a chance to call result() or exception().
|
|
||||||
self._loop.call_soon(self._tb_logger.activate)
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __await__(self):
|
||||||
if not self.done():
|
if not self.done():
|
||||||
self._asyncio_future_blocking = True
|
self._asyncio_future_blocking = True
|
||||||
yield self # This tells Task to wait for completion.
|
yield self # This tells Task to wait for completion.
|
||||||
assert self.done(), "yield from wasn't used with future"
|
if not self.done():
|
||||||
|
raise RuntimeError("await wasn't used with future")
|
||||||
return self.result() # May raise too.
|
return self.result() # May raise too.
|
||||||
|
|
||||||
if compat.PY35:
|
__iter__ = __await__ # make compatible with 'yield from'.
|
||||||
__await__ = __iter__ # make compatible with 'await' expression
|
|
||||||
|
|
||||||
|
|
||||||
# Needed for testing purposes.
|
# Needed for testing purposes.
|
||||||
_PyFuture = Future
|
_PyFuture = Future
|
||||||
|
|
||||||
|
|
||||||
|
def _get_loop(fut):
|
||||||
|
# Tries to call Future.get_loop() if it's available.
|
||||||
|
# Otherwise fallbacks to using the old '_loop' property.
|
||||||
|
try:
|
||||||
|
get_loop = fut.get_loop
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return get_loop()
|
||||||
|
return fut._loop
|
||||||
|
|
||||||
|
|
||||||
def _set_result_unless_cancelled(fut, result):
|
def _set_result_unless_cancelled(fut, result):
|
||||||
"""Helper setting the result only if the future was not cancelled."""
|
"""Helper setting the result only if the future was not cancelled."""
|
||||||
if fut.cancelled():
|
if fut.cancelled():
|
||||||
@@ -350,6 +315,18 @@ def _set_result_unless_cancelled(fut, result):
|
|||||||
fut.set_result(result)
|
fut.set_result(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_future_exc(exc):
|
||||||
|
exc_class = type(exc)
|
||||||
|
if exc_class is concurrent.futures.CancelledError:
|
||||||
|
return exceptions.CancelledError(*exc.args)
|
||||||
|
elif exc_class is concurrent.futures.TimeoutError:
|
||||||
|
return exceptions.TimeoutError(*exc.args)
|
||||||
|
elif exc_class is concurrent.futures.InvalidStateError:
|
||||||
|
return exceptions.InvalidStateError(*exc.args)
|
||||||
|
else:
|
||||||
|
return exc
|
||||||
|
|
||||||
|
|
||||||
def _set_concurrent_future_state(concurrent, source):
|
def _set_concurrent_future_state(concurrent, source):
|
||||||
"""Copy state from a future to a concurrent.futures.Future."""
|
"""Copy state from a future to a concurrent.futures.Future."""
|
||||||
assert source.done()
|
assert source.done()
|
||||||
@@ -359,7 +336,7 @@ def _set_concurrent_future_state(concurrent, source):
|
|||||||
return
|
return
|
||||||
exception = source.exception()
|
exception = source.exception()
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
concurrent.set_exception(exception)
|
concurrent.set_exception(_convert_future_exc(exception))
|
||||||
else:
|
else:
|
||||||
result = source.result()
|
result = source.result()
|
||||||
concurrent.set_result(result)
|
concurrent.set_result(result)
|
||||||
@@ -379,7 +356,7 @@ def _copy_future_state(source, dest):
|
|||||||
else:
|
else:
|
||||||
exception = source.exception()
|
exception = source.exception()
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
dest.set_exception(exception)
|
dest.set_exception(_convert_future_exc(exception))
|
||||||
else:
|
else:
|
||||||
result = source.result()
|
result = source.result()
|
||||||
dest.set_result(result)
|
dest.set_result(result)
|
||||||
@@ -398,8 +375,8 @@ def _chain_future(source, destination):
|
|||||||
if not isfuture(destination) and not isinstance(destination,
|
if not isfuture(destination) and not isinstance(destination,
|
||||||
concurrent.futures.Future):
|
concurrent.futures.Future):
|
||||||
raise TypeError('A future is required for destination argument')
|
raise TypeError('A future is required for destination argument')
|
||||||
source_loop = source._loop if isfuture(source) else None
|
source_loop = _get_loop(source) if isfuture(source) else None
|
||||||
dest_loop = destination._loop if isfuture(destination) else None
|
dest_loop = _get_loop(destination) if isfuture(destination) else None
|
||||||
|
|
||||||
def _set_state(future, other):
|
def _set_state(future, other):
|
||||||
if isfuture(future):
|
if isfuture(future):
|
||||||
@@ -415,9 +392,14 @@ def _chain_future(source, destination):
|
|||||||
source_loop.call_soon_threadsafe(source.cancel)
|
source_loop.call_soon_threadsafe(source.cancel)
|
||||||
|
|
||||||
def _call_set_state(source):
|
def _call_set_state(source):
|
||||||
|
if (destination.cancelled() and
|
||||||
|
dest_loop is not None and dest_loop.is_closed()):
|
||||||
|
return
|
||||||
if dest_loop is None or dest_loop is source_loop:
|
if dest_loop is None or dest_loop is source_loop:
|
||||||
_set_state(destination, source)
|
_set_state(destination, source)
|
||||||
else:
|
else:
|
||||||
|
if dest_loop.is_closed():
|
||||||
|
return
|
||||||
dest_loop.call_soon_threadsafe(_set_state, destination, source)
|
dest_loop.call_soon_threadsafe(_set_state, destination, source)
|
||||||
|
|
||||||
destination.add_done_callback(_call_check_cancel)
|
destination.add_done_callback(_call_check_cancel)
|
||||||
@@ -429,7 +411,7 @@ def wrap_future(future, *, loop=None):
|
|||||||
if isfuture(future):
|
if isfuture(future):
|
||||||
return future
|
return future
|
||||||
assert isinstance(future, concurrent.futures.Future), \
|
assert isinstance(future, concurrent.futures.Future), \
|
||||||
'concurrent.futures.Future is expected, got {!r}'.format(future)
|
f'concurrent.futures.Future is expected, got {future!r}'
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = events.get_event_loop()
|
loop = events.get_event_loop()
|
||||||
new_future = loop.create_future()
|
new_future = loop.create_future()
|
||||||
|
|||||||
456
Lib/asyncio/locks.py
vendored
456
Lib/asyncio/locks.py
vendored
@@ -1,92 +1,26 @@
|
|||||||
"""Synchronization primitives."""
|
"""Synchronization primitives."""
|
||||||
|
|
||||||
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
|
__all__ = ('Lock', 'Event', 'Condition', 'Semaphore',
|
||||||
|
'BoundedSemaphore', 'Barrier')
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import enum
|
||||||
|
|
||||||
from . import compat
|
from . import exceptions
|
||||||
from . import events
|
from . import mixins
|
||||||
from . import futures
|
|
||||||
from .coroutines import coroutine
|
|
||||||
|
|
||||||
|
class _ContextManagerMixin:
|
||||||
class _ContextManager:
|
async def __aenter__(self):
|
||||||
"""Context manager.
|
await self.acquire()
|
||||||
|
|
||||||
This enables the following idiom for acquiring and releasing a
|
|
||||||
lock around a block:
|
|
||||||
|
|
||||||
with (yield from lock):
|
|
||||||
<block>
|
|
||||||
|
|
||||||
while failing loudly when accidentally using:
|
|
||||||
|
|
||||||
with lock:
|
|
||||||
<block>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, lock):
|
|
||||||
self._lock = lock
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
# We have no use for the "as ..." clause in the with
|
# We have no use for the "as ..." clause in the with
|
||||||
# statement for locks.
|
# statement for locks.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __exit__(self, *args):
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
try:
|
self.release()
|
||||||
self._lock.release()
|
|
||||||
finally:
|
|
||||||
self._lock = None # Crudely prevent reuse.
|
|
||||||
|
|
||||||
|
|
||||||
class _ContextManagerMixin:
|
class Lock(_ContextManagerMixin, mixins._LoopBoundMixin):
|
||||||
def __enter__(self):
|
|
||||||
raise RuntimeError(
|
|
||||||
'"yield from" should be used as context manager expression')
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
# This must exist because __enter__ exists, even though that
|
|
||||||
# always raises; that's how the with-statement works.
|
|
||||||
pass
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __iter__(self):
|
|
||||||
# This is not a coroutine. It is meant to enable the idiom:
|
|
||||||
#
|
|
||||||
# with (yield from lock):
|
|
||||||
# <block>
|
|
||||||
#
|
|
||||||
# as an alternative to:
|
|
||||||
#
|
|
||||||
# yield from lock.acquire()
|
|
||||||
# try:
|
|
||||||
# <block>
|
|
||||||
# finally:
|
|
||||||
# lock.release()
|
|
||||||
yield from self.acquire()
|
|
||||||
return _ContextManager(self)
|
|
||||||
|
|
||||||
if compat.PY35:
|
|
||||||
|
|
||||||
def __await__(self):
|
|
||||||
# To make "with await lock" work.
|
|
||||||
yield from self.acquire()
|
|
||||||
return _ContextManager(self)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __aenter__(self):
|
|
||||||
yield from self.acquire()
|
|
||||||
# We have no use for the "as ..." clause in the with
|
|
||||||
# statement for locks.
|
|
||||||
return None
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __aexit__(self, exc_type, exc, tb):
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
|
|
||||||
class Lock(_ContextManagerMixin):
|
|
||||||
"""Primitive lock objects.
|
"""Primitive lock objects.
|
||||||
|
|
||||||
A primitive lock is a synchronization primitive that is not owned
|
A primitive lock is a synchronization primitive that is not owned
|
||||||
@@ -108,16 +42,16 @@ class Lock(_ContextManagerMixin):
|
|||||||
release() call resets the state to unlocked; first coroutine which
|
release() call resets the state to unlocked; first coroutine which
|
||||||
is blocked in acquire() is being processed.
|
is blocked in acquire() is being processed.
|
||||||
|
|
||||||
acquire() is a coroutine and should be called with 'yield from'.
|
acquire() is a coroutine and should be called with 'await'.
|
||||||
|
|
||||||
Locks also support the context management protocol. '(yield from lock)'
|
Locks also support the asynchronous context management protocol.
|
||||||
should be used as the context manager expression.
|
'async with lock' statement should be used.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
...
|
...
|
||||||
yield from lock
|
await lock.acquire()
|
||||||
try:
|
try:
|
||||||
...
|
...
|
||||||
finally:
|
finally:
|
||||||
@@ -127,57 +61,65 @@ class Lock(_ContextManagerMixin):
|
|||||||
|
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
...
|
...
|
||||||
with (yield from lock):
|
async with lock:
|
||||||
...
|
...
|
||||||
|
|
||||||
Lock objects can be tested for locking state:
|
Lock objects can be tested for locking state:
|
||||||
|
|
||||||
if not lock.locked():
|
if not lock.locked():
|
||||||
yield from lock
|
await lock.acquire()
|
||||||
else:
|
else:
|
||||||
# lock is acquired
|
# lock is acquired
|
||||||
...
|
...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, loop=None):
|
def __init__(self):
|
||||||
self._waiters = collections.deque()
|
self._waiters = None
|
||||||
self._locked = False
|
self._locked = False
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
res = super().__repr__()
|
res = super().__repr__()
|
||||||
extra = 'locked' if self._locked else 'unlocked'
|
extra = 'locked' if self._locked else 'unlocked'
|
||||||
if self._waiters:
|
if self._waiters:
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
return f'<{res[1:-1]} [{extra}]>'
|
||||||
|
|
||||||
def locked(self):
|
def locked(self):
|
||||||
"""Return True if lock is acquired."""
|
"""Return True if lock is acquired."""
|
||||||
return self._locked
|
return self._locked
|
||||||
|
|
||||||
@coroutine
|
async def acquire(self):
|
||||||
def acquire(self):
|
|
||||||
"""Acquire a lock.
|
"""Acquire a lock.
|
||||||
|
|
||||||
This method blocks until the lock is unlocked, then sets it to
|
This method blocks until the lock is unlocked, then sets it to
|
||||||
locked and returns True.
|
locked and returns True.
|
||||||
"""
|
"""
|
||||||
if not self._locked and all(w.cancelled() for w in self._waiters):
|
if (not self._locked and (self._waiters is None or
|
||||||
|
all(w.cancelled() for w in self._waiters))):
|
||||||
self._locked = True
|
self._locked = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
fut = self._loop.create_future()
|
if self._waiters is None:
|
||||||
|
self._waiters = collections.deque()
|
||||||
|
fut = self._get_loop().create_future()
|
||||||
self._waiters.append(fut)
|
self._waiters.append(fut)
|
||||||
|
|
||||||
|
# Finally block should be called before the CancelledError
|
||||||
|
# handling as we don't want CancelledError to call
|
||||||
|
# _wake_up_first() and attempt to wake up itself.
|
||||||
try:
|
try:
|
||||||
yield from fut
|
try:
|
||||||
self._locked = True
|
await fut
|
||||||
return True
|
finally:
|
||||||
finally:
|
self._waiters.remove(fut)
|
||||||
self._waiters.remove(fut)
|
except exceptions.CancelledError:
|
||||||
|
if not self._locked:
|
||||||
|
self._wake_up_first()
|
||||||
|
raise
|
||||||
|
|
||||||
|
self._locked = True
|
||||||
|
return True
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
"""Release a lock.
|
"""Release a lock.
|
||||||
@@ -192,16 +134,27 @@ class Lock(_ContextManagerMixin):
|
|||||||
"""
|
"""
|
||||||
if self._locked:
|
if self._locked:
|
||||||
self._locked = False
|
self._locked = False
|
||||||
# Wake up the first waiter who isn't cancelled.
|
self._wake_up_first()
|
||||||
for fut in self._waiters:
|
|
||||||
if not fut.done():
|
|
||||||
fut.set_result(True)
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Lock is not acquired.')
|
raise RuntimeError('Lock is not acquired.')
|
||||||
|
|
||||||
|
def _wake_up_first(self):
|
||||||
|
"""Wake up the first waiter if it isn't done."""
|
||||||
|
if not self._waiters:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
fut = next(iter(self._waiters))
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
|
||||||
class Event:
|
# .done() necessarily means that a waiter will wake up later on and
|
||||||
|
# either take the lock, or, if it was cancelled and lock wasn't
|
||||||
|
# taken already, will hit this again and wake up a new waiter.
|
||||||
|
if not fut.done():
|
||||||
|
fut.set_result(True)
|
||||||
|
|
||||||
|
|
||||||
|
class Event(mixins._LoopBoundMixin):
|
||||||
"""Asynchronous equivalent to threading.Event.
|
"""Asynchronous equivalent to threading.Event.
|
||||||
|
|
||||||
Class implementing event objects. An event manages a flag that can be set
|
Class implementing event objects. An event manages a flag that can be set
|
||||||
@@ -210,20 +163,16 @@ class Event:
|
|||||||
false.
|
false.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, loop=None):
|
def __init__(self):
|
||||||
self._waiters = collections.deque()
|
self._waiters = collections.deque()
|
||||||
self._value = False
|
self._value = False
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
res = super().__repr__()
|
res = super().__repr__()
|
||||||
extra = 'set' if self._value else 'unset'
|
extra = 'set' if self._value else 'unset'
|
||||||
if self._waiters:
|
if self._waiters:
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
return f'<{res[1:-1]} [{extra}]>'
|
||||||
|
|
||||||
def is_set(self):
|
def is_set(self):
|
||||||
"""Return True if and only if the internal flag is true."""
|
"""Return True if and only if the internal flag is true."""
|
||||||
@@ -247,8 +196,7 @@ class Event:
|
|||||||
to true again."""
|
to true again."""
|
||||||
self._value = False
|
self._value = False
|
||||||
|
|
||||||
@coroutine
|
async def wait(self):
|
||||||
def wait(self):
|
|
||||||
"""Block until the internal flag is true.
|
"""Block until the internal flag is true.
|
||||||
|
|
||||||
If the internal flag is true on entry, return True
|
If the internal flag is true on entry, return True
|
||||||
@@ -258,16 +206,16 @@ class Event:
|
|||||||
if self._value:
|
if self._value:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
fut = self._loop.create_future()
|
fut = self._get_loop().create_future()
|
||||||
self._waiters.append(fut)
|
self._waiters.append(fut)
|
||||||
try:
|
try:
|
||||||
yield from fut
|
await fut
|
||||||
return True
|
return True
|
||||||
finally:
|
finally:
|
||||||
self._waiters.remove(fut)
|
self._waiters.remove(fut)
|
||||||
|
|
||||||
|
|
||||||
class Condition(_ContextManagerMixin):
|
class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
|
||||||
"""Asynchronous equivalent to threading.Condition.
|
"""Asynchronous equivalent to threading.Condition.
|
||||||
|
|
||||||
This class implements condition variable objects. A condition variable
|
This class implements condition variable objects. A condition variable
|
||||||
@@ -277,16 +225,9 @@ class Condition(_ContextManagerMixin):
|
|||||||
A new Lock object is created and used as the underlying lock.
|
A new Lock object is created and used as the underlying lock.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, lock=None, *, loop=None):
|
def __init__(self, lock=None):
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
if lock is None:
|
if lock is None:
|
||||||
lock = Lock(loop=self._loop)
|
lock = Lock()
|
||||||
elif lock._loop is not self._loop:
|
|
||||||
raise ValueError("loop argument must agree with lock")
|
|
||||||
|
|
||||||
self._lock = lock
|
self._lock = lock
|
||||||
# Export the lock's locked(), acquire() and release() methods.
|
# Export the lock's locked(), acquire() and release() methods.
|
||||||
@@ -300,11 +241,10 @@ class Condition(_ContextManagerMixin):
|
|||||||
res = super().__repr__()
|
res = super().__repr__()
|
||||||
extra = 'locked' if self.locked() else 'unlocked'
|
extra = 'locked' if self.locked() else 'unlocked'
|
||||||
if self._waiters:
|
if self._waiters:
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
return f'<{res[1:-1]} [{extra}]>'
|
||||||
|
|
||||||
@coroutine
|
async def wait(self):
|
||||||
def wait(self):
|
|
||||||
"""Wait until notified.
|
"""Wait until notified.
|
||||||
|
|
||||||
If the calling coroutine has not acquired the lock when this
|
If the calling coroutine has not acquired the lock when this
|
||||||
@@ -320,25 +260,28 @@ class Condition(_ContextManagerMixin):
|
|||||||
|
|
||||||
self.release()
|
self.release()
|
||||||
try:
|
try:
|
||||||
fut = self._loop.create_future()
|
fut = self._get_loop().create_future()
|
||||||
self._waiters.append(fut)
|
self._waiters.append(fut)
|
||||||
try:
|
try:
|
||||||
yield from fut
|
await fut
|
||||||
return True
|
return True
|
||||||
finally:
|
finally:
|
||||||
self._waiters.remove(fut)
|
self._waiters.remove(fut)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Must reacquire lock even if wait is cancelled
|
# Must reacquire lock even if wait is cancelled
|
||||||
|
cancelled = False
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
yield from self.acquire()
|
await self.acquire()
|
||||||
break
|
break
|
||||||
except futures.CancelledError:
|
except exceptions.CancelledError:
|
||||||
pass
|
cancelled = True
|
||||||
|
|
||||||
@coroutine
|
if cancelled:
|
||||||
def wait_for(self, predicate):
|
raise exceptions.CancelledError
|
||||||
|
|
||||||
|
async def wait_for(self, predicate):
|
||||||
"""Wait until a predicate becomes true.
|
"""Wait until a predicate becomes true.
|
||||||
|
|
||||||
The predicate should be a callable which result will be
|
The predicate should be a callable which result will be
|
||||||
@@ -347,7 +290,7 @@ class Condition(_ContextManagerMixin):
|
|||||||
"""
|
"""
|
||||||
result = predicate()
|
result = predicate()
|
||||||
while not result:
|
while not result:
|
||||||
yield from self.wait()
|
await self.wait()
|
||||||
result = predicate()
|
result = predicate()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -384,7 +327,7 @@ class Condition(_ContextManagerMixin):
|
|||||||
self.notify(len(self._waiters))
|
self.notify(len(self._waiters))
|
||||||
|
|
||||||
|
|
||||||
class Semaphore(_ContextManagerMixin):
|
class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin):
|
||||||
"""A Semaphore implementation.
|
"""A Semaphore implementation.
|
||||||
|
|
||||||
A semaphore manages an internal counter which is decremented by each
|
A semaphore manages an internal counter which is decremented by each
|
||||||
@@ -399,37 +342,25 @@ class Semaphore(_ContextManagerMixin):
|
|||||||
ValueError is raised.
|
ValueError is raised.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, value=1, *, loop=None):
|
def __init__(self, value=1):
|
||||||
if value < 0:
|
if value < 0:
|
||||||
raise ValueError("Semaphore initial value must be >= 0")
|
raise ValueError("Semaphore initial value must be >= 0")
|
||||||
|
self._waiters = None
|
||||||
self._value = value
|
self._value = value
|
||||||
self._waiters = collections.deque()
|
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
res = super().__repr__()
|
res = super().__repr__()
|
||||||
extra = 'locked' if self.locked() else 'unlocked,value:{}'.format(
|
extra = 'locked' if self.locked() else f'unlocked, value:{self._value}'
|
||||||
self._value)
|
|
||||||
if self._waiters:
|
if self._waiters:
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
return f'<{res[1:-1]} [{extra}]>'
|
||||||
|
|
||||||
def _wake_up_next(self):
|
|
||||||
while self._waiters:
|
|
||||||
waiter = self._waiters.popleft()
|
|
||||||
if not waiter.done():
|
|
||||||
waiter.set_result(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
def locked(self):
|
def locked(self):
|
||||||
"""Returns True if semaphore can not be acquired immediately."""
|
"""Returns True if semaphore cannot be acquired immediately."""
|
||||||
return self._value == 0
|
return self._value == 0 or (
|
||||||
|
any(not w.cancelled() for w in (self._waiters or ())))
|
||||||
|
|
||||||
@coroutine
|
async def acquire(self):
|
||||||
def acquire(self):
|
|
||||||
"""Acquire a semaphore.
|
"""Acquire a semaphore.
|
||||||
|
|
||||||
If the internal counter is larger than zero on entry,
|
If the internal counter is larger than zero on entry,
|
||||||
@@ -438,28 +369,53 @@ class Semaphore(_ContextManagerMixin):
|
|||||||
called release() to make it larger than 0, and then return
|
called release() to make it larger than 0, and then return
|
||||||
True.
|
True.
|
||||||
"""
|
"""
|
||||||
while self._value <= 0:
|
if not self.locked():
|
||||||
fut = self._loop.create_future()
|
self._value -= 1
|
||||||
self._waiters.append(fut)
|
return True
|
||||||
|
|
||||||
|
if self._waiters is None:
|
||||||
|
self._waiters = collections.deque()
|
||||||
|
fut = self._get_loop().create_future()
|
||||||
|
self._waiters.append(fut)
|
||||||
|
|
||||||
|
# Finally block should be called before the CancelledError
|
||||||
|
# handling as we don't want CancelledError to call
|
||||||
|
# _wake_up_first() and attempt to wake up itself.
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
yield from fut
|
await fut
|
||||||
except:
|
finally:
|
||||||
# See the similar code in Queue.get.
|
self._waiters.remove(fut)
|
||||||
fut.cancel()
|
except exceptions.CancelledError:
|
||||||
if self._value > 0 and not fut.cancelled():
|
if not fut.cancelled():
|
||||||
self._wake_up_next()
|
self._value += 1
|
||||||
raise
|
self._wake_up_next()
|
||||||
self._value -= 1
|
raise
|
||||||
|
|
||||||
|
if self._value > 0:
|
||||||
|
self._wake_up_next()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
"""Release a semaphore, incrementing the internal counter by one.
|
"""Release a semaphore, incrementing the internal counter by one.
|
||||||
|
|
||||||
When it was zero on entry and another coroutine is waiting for it to
|
When it was zero on entry and another coroutine is waiting for it to
|
||||||
become larger than zero again, wake up that coroutine.
|
become larger than zero again, wake up that coroutine.
|
||||||
"""
|
"""
|
||||||
self._value += 1
|
self._value += 1
|
||||||
self._wake_up_next()
|
self._wake_up_next()
|
||||||
|
|
||||||
|
def _wake_up_next(self):
|
||||||
|
"""Wake up the first waiter that isn't done."""
|
||||||
|
if not self._waiters:
|
||||||
|
return
|
||||||
|
|
||||||
|
for fut in self._waiters:
|
||||||
|
if not fut.done():
|
||||||
|
self._value -= 1
|
||||||
|
fut.set_result(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class BoundedSemaphore(Semaphore):
|
class BoundedSemaphore(Semaphore):
|
||||||
"""A bounded semaphore implementation.
|
"""A bounded semaphore implementation.
|
||||||
@@ -468,11 +424,163 @@ class BoundedSemaphore(Semaphore):
|
|||||||
above the initial value.
|
above the initial value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, value=1, *, loop=None):
|
def __init__(self, value=1):
|
||||||
self._bound_value = value
|
self._bound_value = value
|
||||||
super().__init__(value, loop=loop)
|
super().__init__(value)
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
if self._value >= self._bound_value:
|
if self._value >= self._bound_value:
|
||||||
raise ValueError('BoundedSemaphore released too many times')
|
raise ValueError('BoundedSemaphore released too many times')
|
||||||
super().release()
|
super().release()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class _BarrierState(enum.Enum):
|
||||||
|
FILLING = 'filling'
|
||||||
|
DRAINING = 'draining'
|
||||||
|
RESETTING = 'resetting'
|
||||||
|
BROKEN = 'broken'
|
||||||
|
|
||||||
|
|
||||||
|
class Barrier(mixins._LoopBoundMixin):
|
||||||
|
"""Asyncio equivalent to threading.Barrier
|
||||||
|
|
||||||
|
Implements a Barrier primitive.
|
||||||
|
Useful for synchronizing a fixed number of tasks at known synchronization
|
||||||
|
points. Tasks block on 'wait()' and are simultaneously awoken once they
|
||||||
|
have all made their call.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parties):
|
||||||
|
"""Create a barrier, initialised to 'parties' tasks."""
|
||||||
|
if parties < 1:
|
||||||
|
raise ValueError('parties must be > 0')
|
||||||
|
|
||||||
|
self._cond = Condition() # notify all tasks when state changes
|
||||||
|
|
||||||
|
self._parties = parties
|
||||||
|
self._state = _BarrierState.FILLING
|
||||||
|
self._count = 0 # count tasks in Barrier
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
res = super().__repr__()
|
||||||
|
extra = f'{self._state.value}'
|
||||||
|
if not self.broken:
|
||||||
|
extra += f', waiters:{self.n_waiting}/{self.parties}'
|
||||||
|
return f'<{res[1:-1]} [{extra}]>'
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
# wait for the barrier reaches the parties number
|
||||||
|
# when start draining release and return index of waited task
|
||||||
|
return await self.wait()
|
||||||
|
|
||||||
|
async def __aexit__(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
"""Wait for the barrier.
|
||||||
|
|
||||||
|
When the specified number of tasks have started waiting, they are all
|
||||||
|
simultaneously awoken.
|
||||||
|
Returns an unique and individual index number from 0 to 'parties-1'.
|
||||||
|
"""
|
||||||
|
async with self._cond:
|
||||||
|
await self._block() # Block while the barrier drains or resets.
|
||||||
|
try:
|
||||||
|
index = self._count
|
||||||
|
self._count += 1
|
||||||
|
if index + 1 == self._parties:
|
||||||
|
# We release the barrier
|
||||||
|
await self._release()
|
||||||
|
else:
|
||||||
|
await self._wait()
|
||||||
|
return index
|
||||||
|
finally:
|
||||||
|
self._count -= 1
|
||||||
|
# Wake up any tasks waiting for barrier to drain.
|
||||||
|
self._exit()
|
||||||
|
|
||||||
|
async def _block(self):
|
||||||
|
# Block until the barrier is ready for us,
|
||||||
|
# or raise an exception if it is broken.
|
||||||
|
#
|
||||||
|
# It is draining or resetting, wait until done
|
||||||
|
# unless a CancelledError occurs
|
||||||
|
await self._cond.wait_for(
|
||||||
|
lambda: self._state not in (
|
||||||
|
_BarrierState.DRAINING, _BarrierState.RESETTING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# see if the barrier is in a broken state
|
||||||
|
if self._state is _BarrierState.BROKEN:
|
||||||
|
raise exceptions.BrokenBarrierError("Barrier aborted")
|
||||||
|
|
||||||
|
async def _release(self):
|
||||||
|
# Release the tasks waiting in the barrier.
|
||||||
|
|
||||||
|
# Enter draining state.
|
||||||
|
# Next waiting tasks will be blocked until the end of draining.
|
||||||
|
self._state = _BarrierState.DRAINING
|
||||||
|
self._cond.notify_all()
|
||||||
|
|
||||||
|
async def _wait(self):
|
||||||
|
# Wait in the barrier until we are released. Raise an exception
|
||||||
|
# if the barrier is reset or broken.
|
||||||
|
|
||||||
|
# wait for end of filling
|
||||||
|
# unless a CancelledError occurs
|
||||||
|
await self._cond.wait_for(lambda: self._state is not _BarrierState.FILLING)
|
||||||
|
|
||||||
|
if self._state in (_BarrierState.BROKEN, _BarrierState.RESETTING):
|
||||||
|
raise exceptions.BrokenBarrierError("Abort or reset of barrier")
|
||||||
|
|
||||||
|
def _exit(self):
|
||||||
|
# If we are the last tasks to exit the barrier, signal any tasks
|
||||||
|
# waiting for the barrier to drain.
|
||||||
|
if self._count == 0:
|
||||||
|
if self._state in (_BarrierState.RESETTING, _BarrierState.DRAINING):
|
||||||
|
self._state = _BarrierState.FILLING
|
||||||
|
self._cond.notify_all()
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""Reset the barrier to the initial state.
|
||||||
|
|
||||||
|
Any tasks currently waiting will get the BrokenBarrier exception
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
async with self._cond:
|
||||||
|
if self._count > 0:
|
||||||
|
if self._state is not _BarrierState.RESETTING:
|
||||||
|
#reset the barrier, waking up tasks
|
||||||
|
self._state = _BarrierState.RESETTING
|
||||||
|
else:
|
||||||
|
self._state = _BarrierState.FILLING
|
||||||
|
self._cond.notify_all()
|
||||||
|
|
||||||
|
async def abort(self):
|
||||||
|
"""Place the barrier into a 'broken' state.
|
||||||
|
|
||||||
|
Useful in case of error. Any currently waiting tasks and tasks
|
||||||
|
attempting to 'wait()' will have BrokenBarrierError raised.
|
||||||
|
"""
|
||||||
|
async with self._cond:
|
||||||
|
self._state = _BarrierState.BROKEN
|
||||||
|
self._cond.notify_all()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parties(self):
|
||||||
|
"""Return the number of tasks required to trip the barrier."""
|
||||||
|
return self._parties
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_waiting(self):
|
||||||
|
"""Return the number of tasks currently waiting at the barrier."""
|
||||||
|
if self._state is _BarrierState.FILLING:
|
||||||
|
return self._count
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def broken(self):
|
||||||
|
"""Return True if the barrier is in a broken state."""
|
||||||
|
return self._state is _BarrierState.BROKEN
|
||||||
|
|||||||
21
Lib/asyncio/mixins.py
vendored
Normal file
21
Lib/asyncio/mixins.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""Event loop mixins."""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
from . import events
|
||||||
|
|
||||||
|
_global_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class _LoopBoundMixin:
|
||||||
|
_loop = None
|
||||||
|
|
||||||
|
def _get_loop(self):
|
||||||
|
loop = events._get_running_loop()
|
||||||
|
|
||||||
|
if self._loop is None:
|
||||||
|
with _global_lock:
|
||||||
|
if self._loop is None:
|
||||||
|
self._loop = loop
|
||||||
|
if loop is not self._loop:
|
||||||
|
raise RuntimeError(f'{self!r} is bound to a different event loop')
|
||||||
|
return loop
|
||||||
563
Lib/asyncio/proactor_events.py
vendored
563
Lib/asyncio/proactor_events.py
vendored
@@ -4,20 +4,45 @@ A proactor is a "notify-on-completion" multiplexer. Currently a
|
|||||||
proactor is only implemented on Windows with IOCP.
|
proactor is only implemented on Windows with IOCP.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ['BaseProactorEventLoop']
|
__all__ = 'BaseProactorEventLoop',
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
import socket
|
import socket
|
||||||
import warnings
|
import warnings
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
import collections
|
||||||
|
|
||||||
from . import base_events
|
from . import base_events
|
||||||
from . import compat
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import futures
|
from . import futures
|
||||||
|
from . import exceptions
|
||||||
|
from . import protocols
|
||||||
from . import sslproto
|
from . import sslproto
|
||||||
from . import transports
|
from . import transports
|
||||||
|
from . import trsock
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
|
|
||||||
|
def _set_socket_extra(transport, sock):
|
||||||
|
transport._extra['socket'] = trsock.TransportSocket(sock)
|
||||||
|
|
||||||
|
try:
|
||||||
|
transport._extra['sockname'] = sock.getsockname()
|
||||||
|
except socket.error:
|
||||||
|
if transport._loop.get_debug():
|
||||||
|
logger.warning(
|
||||||
|
"getsockname() failed on %r", sock, exc_info=True)
|
||||||
|
|
||||||
|
if 'peername' not in transport._extra:
|
||||||
|
try:
|
||||||
|
transport._extra['peername'] = sock.getpeername()
|
||||||
|
except socket.error:
|
||||||
|
# UDP sockets may not have a peer name
|
||||||
|
transport._extra['peername'] = None
|
||||||
|
|
||||||
|
|
||||||
class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||||
transports.BaseTransport):
|
transports.BaseTransport):
|
||||||
"""Base class for pipe and socket transports."""
|
"""Base class for pipe and socket transports."""
|
||||||
@@ -27,7 +52,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
super().__init__(extra, loop)
|
super().__init__(extra, loop)
|
||||||
self._set_extra(sock)
|
self._set_extra(sock)
|
||||||
self._sock = sock
|
self._sock = sock
|
||||||
self._protocol = protocol
|
self.set_protocol(protocol)
|
||||||
self._server = server
|
self._server = server
|
||||||
self._buffer = None # None or bytearray.
|
self._buffer = None # None or bytearray.
|
||||||
self._read_fut = None
|
self._read_fut = None
|
||||||
@@ -35,6 +60,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
self._pending_write = 0
|
self._pending_write = 0
|
||||||
self._conn_lost = 0
|
self._conn_lost = 0
|
||||||
self._closing = False # Set when close() called.
|
self._closing = False # Set when close() called.
|
||||||
|
self._called_connection_lost = False
|
||||||
self._eof_written = False
|
self._eof_written = False
|
||||||
if self._server is not None:
|
if self._server is not None:
|
||||||
self._server._attach()
|
self._server._attach()
|
||||||
@@ -51,17 +77,16 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
elif self._closing:
|
elif self._closing:
|
||||||
info.append('closing')
|
info.append('closing')
|
||||||
if self._sock is not None:
|
if self._sock is not None:
|
||||||
info.append('fd=%s' % self._sock.fileno())
|
info.append(f'fd={self._sock.fileno()}')
|
||||||
if self._read_fut is not None:
|
if self._read_fut is not None:
|
||||||
info.append('read=%s' % self._read_fut)
|
info.append(f'read={self._read_fut!r}')
|
||||||
if self._write_fut is not None:
|
if self._write_fut is not None:
|
||||||
info.append("write=%r" % self._write_fut)
|
info.append(f'write={self._write_fut!r}')
|
||||||
if self._buffer:
|
if self._buffer:
|
||||||
bufsize = len(self._buffer)
|
info.append(f'write_bufsize={len(self._buffer)}')
|
||||||
info.append('write_bufsize=%s' % bufsize)
|
|
||||||
if self._eof_written:
|
if self._eof_written:
|
||||||
info.append('EOF written')
|
info.append('EOF written')
|
||||||
return '<%s>' % ' '.join(info)
|
return '<{}>'.format(' '.join(info))
|
||||||
|
|
||||||
def _set_extra(self, sock):
|
def _set_extra(self, sock):
|
||||||
self._extra['pipe'] = sock
|
self._extra['pipe'] = sock
|
||||||
@@ -86,31 +111,33 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
self._read_fut.cancel()
|
self._read_fut.cancel()
|
||||||
self._read_fut = None
|
self._read_fut = None
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
def __del__(self, _warn=warnings.warn):
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
if self._sock is not None:
|
||||||
# to the PEP 442.
|
_warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
|
||||||
if compat.PY34:
|
self._sock.close()
|
||||||
def __del__(self):
|
|
||||||
if self._sock is not None:
|
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
|
||||||
source=self)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def _fatal_error(self, exc, message='Fatal error on pipe transport'):
|
def _fatal_error(self, exc, message='Fatal error on pipe transport'):
|
||||||
if isinstance(exc, base_events._FATAL_ERROR_IGNORE):
|
try:
|
||||||
if self._loop.get_debug():
|
if isinstance(exc, OSError):
|
||||||
logger.debug("%r: %s", self, message, exc_info=True)
|
if self._loop.get_debug():
|
||||||
else:
|
logger.debug("%r: %s", self, message, exc_info=True)
|
||||||
self._loop.call_exception_handler({
|
else:
|
||||||
'message': message,
|
self._loop.call_exception_handler({
|
||||||
'exception': exc,
|
'message': message,
|
||||||
'transport': self,
|
'exception': exc,
|
||||||
'protocol': self._protocol,
|
'transport': self,
|
||||||
})
|
'protocol': self._protocol,
|
||||||
self._force_close(exc)
|
})
|
||||||
|
finally:
|
||||||
|
self._force_close(exc)
|
||||||
|
|
||||||
def _force_close(self, exc):
|
def _force_close(self, exc):
|
||||||
if self._closing:
|
if self._empty_waiter is not None and not self._empty_waiter.done():
|
||||||
|
if exc is None:
|
||||||
|
self._empty_waiter.set_result(None)
|
||||||
|
else:
|
||||||
|
self._empty_waiter.set_exception(exc)
|
||||||
|
if self._closing and self._called_connection_lost:
|
||||||
return
|
return
|
||||||
self._closing = True
|
self._closing = True
|
||||||
self._conn_lost += 1
|
self._conn_lost += 1
|
||||||
@@ -125,6 +152,8 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
self._loop.call_soon(self._call_connection_lost, exc)
|
self._loop.call_soon(self._call_connection_lost, exc)
|
||||||
|
|
||||||
def _call_connection_lost(self, exc):
|
def _call_connection_lost(self, exc):
|
||||||
|
if self._called_connection_lost:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self._protocol.connection_lost(exc)
|
self._protocol.connection_lost(exc)
|
||||||
finally:
|
finally:
|
||||||
@@ -132,7 +161,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
# end then it may fail with ERROR_NETNAME_DELETED if we
|
# end then it may fail with ERROR_NETNAME_DELETED if we
|
||||||
# just close our end. First calling shutdown() seems to
|
# just close our end. First calling shutdown() seems to
|
||||||
# cure it, but maybe using DisconnectEx() would be better.
|
# cure it, but maybe using DisconnectEx() would be better.
|
||||||
if hasattr(self._sock, 'shutdown'):
|
if hasattr(self._sock, 'shutdown') and self._sock.fileno() != -1:
|
||||||
self._sock.shutdown(socket.SHUT_RDWR)
|
self._sock.shutdown(socket.SHUT_RDWR)
|
||||||
self._sock.close()
|
self._sock.close()
|
||||||
self._sock = None
|
self._sock = None
|
||||||
@@ -140,6 +169,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|||||||
if server is not None:
|
if server is not None:
|
||||||
server._detach()
|
server._detach()
|
||||||
self._server = None
|
self._server = None
|
||||||
|
self._called_connection_lost = True
|
||||||
|
|
||||||
def get_write_buffer_size(self):
|
def get_write_buffer_size(self):
|
||||||
size = self._pending_write
|
size = self._pending_write
|
||||||
@@ -153,53 +183,127 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
|
|||||||
"""Transport for read pipes."""
|
"""Transport for read pipes."""
|
||||||
|
|
||||||
def __init__(self, loop, sock, protocol, waiter=None,
|
def __init__(self, loop, sock, protocol, waiter=None,
|
||||||
extra=None, server=None):
|
extra=None, server=None, buffer_size=65536):
|
||||||
|
self._pending_data_length = -1
|
||||||
|
self._paused = True
|
||||||
super().__init__(loop, sock, protocol, waiter, extra, server)
|
super().__init__(loop, sock, protocol, waiter, extra, server)
|
||||||
self._paused = False
|
|
||||||
|
self._data = bytearray(buffer_size)
|
||||||
self._loop.call_soon(self._loop_reading)
|
self._loop.call_soon(self._loop_reading)
|
||||||
|
self._paused = False
|
||||||
|
|
||||||
|
def is_reading(self):
|
||||||
|
return not self._paused and not self._closing
|
||||||
|
|
||||||
def pause_reading(self):
|
def pause_reading(self):
|
||||||
if self._closing:
|
if self._closing or self._paused:
|
||||||
raise RuntimeError('Cannot pause_reading() when closing')
|
return
|
||||||
if self._paused:
|
|
||||||
raise RuntimeError('Already paused')
|
|
||||||
self._paused = True
|
self._paused = True
|
||||||
|
|
||||||
|
# bpo-33694: Don't cancel self._read_fut because cancelling an
|
||||||
|
# overlapped WSASend() loss silently data with the current proactor
|
||||||
|
# implementation.
|
||||||
|
#
|
||||||
|
# If CancelIoEx() fails with ERROR_NOT_FOUND, it means that WSASend()
|
||||||
|
# completed (even if HasOverlappedIoCompleted() returns 0), but
|
||||||
|
# Overlapped.cancel() currently silently ignores the ERROR_NOT_FOUND
|
||||||
|
# error. Once the overlapped is ignored, the IOCP loop will ignores the
|
||||||
|
# completion I/O event and so not read the result of the overlapped
|
||||||
|
# WSARecv().
|
||||||
|
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
logger.debug("%r pauses reading", self)
|
logger.debug("%r pauses reading", self)
|
||||||
|
|
||||||
def resume_reading(self):
|
def resume_reading(self):
|
||||||
if not self._paused:
|
if self._closing or not self._paused:
|
||||||
raise RuntimeError('Not paused')
|
|
||||||
self._paused = False
|
|
||||||
if self._closing:
|
|
||||||
return
|
return
|
||||||
self._loop.call_soon(self._loop_reading, self._read_fut)
|
|
||||||
|
self._paused = False
|
||||||
|
if self._read_fut is None:
|
||||||
|
self._loop.call_soon(self._loop_reading, None)
|
||||||
|
|
||||||
|
length = self._pending_data_length
|
||||||
|
self._pending_data_length = -1
|
||||||
|
if length > -1:
|
||||||
|
# Call the protocol method after calling _loop_reading(),
|
||||||
|
# since the protocol can decide to pause reading again.
|
||||||
|
self._loop.call_soon(self._data_received, self._data[:length], length)
|
||||||
|
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
logger.debug("%r resumes reading", self)
|
logger.debug("%r resumes reading", self)
|
||||||
|
|
||||||
def _loop_reading(self, fut=None):
|
def _eof_received(self):
|
||||||
if self._paused:
|
if self._loop.get_debug():
|
||||||
return
|
logger.debug("%r received EOF", self)
|
||||||
data = None
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
keep_open = self._protocol.eof_received()
|
||||||
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
|
self._fatal_error(
|
||||||
|
exc, 'Fatal error: protocol.eof_received() call failed.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not keep_open:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _data_received(self, data, length):
|
||||||
|
if self._paused:
|
||||||
|
# Don't call any protocol method while reading is paused.
|
||||||
|
# The protocol will be called on resume_reading().
|
||||||
|
assert self._pending_data_length == -1
|
||||||
|
self._pending_data_length = length
|
||||||
|
return
|
||||||
|
|
||||||
|
if length == 0:
|
||||||
|
self._eof_received()
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(self._protocol, protocols.BufferedProtocol):
|
||||||
|
try:
|
||||||
|
protocols._feed_data_to_buffered_proto(self._protocol, data)
|
||||||
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
|
self._fatal_error(exc,
|
||||||
|
'Fatal error: protocol.buffer_updated() '
|
||||||
|
'call failed.')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self._protocol.data_received(data)
|
||||||
|
|
||||||
|
def _loop_reading(self, fut=None):
|
||||||
|
length = -1
|
||||||
|
data = None
|
||||||
try:
|
try:
|
||||||
if fut is not None:
|
if fut is not None:
|
||||||
assert self._read_fut is fut or (self._read_fut is None and
|
assert self._read_fut is fut or (self._read_fut is None and
|
||||||
self._closing)
|
self._closing)
|
||||||
self._read_fut = None
|
self._read_fut = None
|
||||||
data = fut.result() # deliver data later in "finally" clause
|
if fut.done():
|
||||||
|
# deliver data later in "finally" clause
|
||||||
|
length = fut.result()
|
||||||
|
if length == 0:
|
||||||
|
# we got end-of-file so no need to reschedule a new read
|
||||||
|
return
|
||||||
|
|
||||||
|
# It's a new slice so make it immutable so protocols upstream don't have problems
|
||||||
|
data = bytes(memoryview(self._data)[:length])
|
||||||
|
else:
|
||||||
|
# the future will be replaced by next proactor.recv call
|
||||||
|
fut.cancel()
|
||||||
|
|
||||||
if self._closing:
|
if self._closing:
|
||||||
# since close() has been called we ignore any read data
|
# since close() has been called we ignore any read data
|
||||||
data = None
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if data == b'':
|
# bpo-33694: buffer_updated() has currently no fast path because of
|
||||||
# we got end-of-file so no need to reschedule a new read
|
# a data loss issue caused by overlapped WSASend() cancellation.
|
||||||
return
|
|
||||||
|
|
||||||
# reschedule a new read
|
if not self._paused:
|
||||||
self._read_fut = self._loop._proactor.recv(self._sock, 4096)
|
# reschedule a new read
|
||||||
|
self._read_fut = self._loop._proactor.recv_into(self._sock, self._data)
|
||||||
except ConnectionAbortedError as exc:
|
except ConnectionAbortedError as exc:
|
||||||
if not self._closing:
|
if not self._closing:
|
||||||
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
||||||
@@ -210,32 +314,36 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
|
|||||||
self._force_close(exc)
|
self._force_close(exc)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
||||||
except futures.CancelledError:
|
except exceptions.CancelledError:
|
||||||
if not self._closing:
|
if not self._closing:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
self._read_fut.add_done_callback(self._loop_reading)
|
if not self._paused:
|
||||||
|
self._read_fut.add_done_callback(self._loop_reading)
|
||||||
finally:
|
finally:
|
||||||
if data:
|
if length > -1:
|
||||||
self._protocol.data_received(data)
|
self._data_received(data, length)
|
||||||
elif data is not None:
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r received EOF", self)
|
|
||||||
keep_open = self._protocol.eof_received()
|
|
||||||
if not keep_open:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
||||||
transports.WriteTransport):
|
transports.WriteTransport):
|
||||||
"""Transport for write pipes."""
|
"""Transport for write pipes."""
|
||||||
|
|
||||||
|
_start_tls_compatible = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super().__init__(*args, **kw)
|
||||||
|
self._empty_waiter = None
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if not isinstance(data, (bytes, bytearray, memoryview)):
|
if not isinstance(data, (bytes, bytearray, memoryview)):
|
||||||
raise TypeError('data argument must be byte-ish (%r)',
|
raise TypeError(
|
||||||
type(data))
|
f"data argument must be a bytes-like object, "
|
||||||
|
f"not {type(data).__name__}")
|
||||||
if self._eof_written:
|
if self._eof_written:
|
||||||
raise RuntimeError('write_eof() already called')
|
raise RuntimeError('write_eof() already called')
|
||||||
|
if self._empty_waiter is not None:
|
||||||
|
raise RuntimeError('unable to write; sendfile is in progress')
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
@@ -267,6 +375,10 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
|||||||
|
|
||||||
def _loop_writing(self, f=None, data=None):
|
def _loop_writing(self, f=None, data=None):
|
||||||
try:
|
try:
|
||||||
|
if f is not None and self._write_fut is None and self._closing:
|
||||||
|
# XXX most likely self._force_close() has been called, and
|
||||||
|
# it has set self._write_fut to None.
|
||||||
|
return
|
||||||
assert f is self._write_fut
|
assert f is self._write_fut
|
||||||
self._write_fut = None
|
self._write_fut = None
|
||||||
self._pending_write = 0
|
self._pending_write = 0
|
||||||
@@ -295,6 +407,8 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
|||||||
self._maybe_pause_protocol()
|
self._maybe_pause_protocol()
|
||||||
else:
|
else:
|
||||||
self._write_fut.add_done_callback(self._loop_writing)
|
self._write_fut.add_done_callback(self._loop_writing)
|
||||||
|
if self._empty_waiter is not None and self._write_fut is None:
|
||||||
|
self._empty_waiter.set_result(None)
|
||||||
except ConnectionResetError as exc:
|
except ConnectionResetError as exc:
|
||||||
self._force_close(exc)
|
self._force_close(exc)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
@@ -309,6 +423,17 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
|||||||
def abort(self):
|
def abort(self):
|
||||||
self._force_close(None)
|
self._force_close(None)
|
||||||
|
|
||||||
|
def _make_empty_waiter(self):
|
||||||
|
if self._empty_waiter is not None:
|
||||||
|
raise RuntimeError("Empty waiter is already set")
|
||||||
|
self._empty_waiter = self._loop.create_future()
|
||||||
|
if self._write_fut is None:
|
||||||
|
self._empty_waiter.set_result(None)
|
||||||
|
return self._empty_waiter
|
||||||
|
|
||||||
|
def _reset_empty_waiter(self):
|
||||||
|
self._empty_waiter = None
|
||||||
|
|
||||||
|
|
||||||
class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
|
class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
|
||||||
def __init__(self, *args, **kw):
|
def __init__(self, *args, **kw):
|
||||||
@@ -332,6 +457,138 @@ class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
class _ProactorDatagramTransport(_ProactorBasePipeTransport,
|
||||||
|
transports.DatagramTransport):
|
||||||
|
max_size = 256 * 1024
|
||||||
|
def __init__(self, loop, sock, protocol, address=None,
|
||||||
|
waiter=None, extra=None):
|
||||||
|
self._address = address
|
||||||
|
self._empty_waiter = None
|
||||||
|
self._buffer_size = 0
|
||||||
|
# We don't need to call _protocol.connection_made() since our base
|
||||||
|
# constructor does it for us.
|
||||||
|
super().__init__(loop, sock, protocol, waiter=waiter, extra=extra)
|
||||||
|
|
||||||
|
# The base constructor sets _buffer = None, so we set it here
|
||||||
|
self._buffer = collections.deque()
|
||||||
|
self._loop.call_soon(self._loop_reading)
|
||||||
|
|
||||||
|
def _set_extra(self, sock):
|
||||||
|
_set_socket_extra(self, sock)
|
||||||
|
|
||||||
|
def get_write_buffer_size(self):
|
||||||
|
return self._buffer_size
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
self._force_close(None)
|
||||||
|
|
||||||
|
def sendto(self, data, addr=None):
|
||||||
|
if not isinstance(data, (bytes, bytearray, memoryview)):
|
||||||
|
raise TypeError('data argument must be bytes-like object (%r)',
|
||||||
|
type(data))
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._address is not None and addr not in (None, self._address):
|
||||||
|
raise ValueError(
|
||||||
|
f'Invalid address: must be None or {self._address}')
|
||||||
|
|
||||||
|
if self._conn_lost and self._address:
|
||||||
|
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
|
||||||
|
logger.warning('socket.sendto() raised exception.')
|
||||||
|
self._conn_lost += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ensure that what we buffer is immutable.
|
||||||
|
self._buffer.append((bytes(data), addr))
|
||||||
|
self._buffer_size += len(data)
|
||||||
|
|
||||||
|
if self._write_fut is None:
|
||||||
|
# No current write operations are active, kick one off
|
||||||
|
self._loop_writing()
|
||||||
|
# else: A write operation is already kicked off
|
||||||
|
|
||||||
|
self._maybe_pause_protocol()
|
||||||
|
|
||||||
|
def _loop_writing(self, fut=None):
|
||||||
|
try:
|
||||||
|
if self._conn_lost:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert fut is self._write_fut
|
||||||
|
self._write_fut = None
|
||||||
|
if fut:
|
||||||
|
# We are in a _loop_writing() done callback, get the result
|
||||||
|
fut.result()
|
||||||
|
|
||||||
|
if not self._buffer or (self._conn_lost and self._address):
|
||||||
|
# The connection has been closed
|
||||||
|
if self._closing:
|
||||||
|
self._loop.call_soon(self._call_connection_lost, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
data, addr = self._buffer.popleft()
|
||||||
|
self._buffer_size -= len(data)
|
||||||
|
if self._address is not None:
|
||||||
|
self._write_fut = self._loop._proactor.send(self._sock,
|
||||||
|
data)
|
||||||
|
else:
|
||||||
|
self._write_fut = self._loop._proactor.sendto(self._sock,
|
||||||
|
data,
|
||||||
|
addr=addr)
|
||||||
|
except OSError as exc:
|
||||||
|
self._protocol.error_received(exc)
|
||||||
|
except Exception as exc:
|
||||||
|
self._fatal_error(exc, 'Fatal write error on datagram transport')
|
||||||
|
else:
|
||||||
|
self._write_fut.add_done_callback(self._loop_writing)
|
||||||
|
self._maybe_resume_protocol()
|
||||||
|
|
||||||
|
def _loop_reading(self, fut=None):
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
if self._conn_lost:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert self._read_fut is fut or (self._read_fut is None and
|
||||||
|
self._closing)
|
||||||
|
|
||||||
|
self._read_fut = None
|
||||||
|
if fut is not None:
|
||||||
|
res = fut.result()
|
||||||
|
|
||||||
|
if self._closing:
|
||||||
|
# since close() has been called we ignore any read data
|
||||||
|
data = None
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._address is not None:
|
||||||
|
data, addr = res, self._address
|
||||||
|
else:
|
||||||
|
data, addr = res
|
||||||
|
|
||||||
|
if self._conn_lost:
|
||||||
|
return
|
||||||
|
if self._address is not None:
|
||||||
|
self._read_fut = self._loop._proactor.recv(self._sock,
|
||||||
|
self.max_size)
|
||||||
|
else:
|
||||||
|
self._read_fut = self._loop._proactor.recvfrom(self._sock,
|
||||||
|
self.max_size)
|
||||||
|
except OSError as exc:
|
||||||
|
self._protocol.error_received(exc)
|
||||||
|
except exceptions.CancelledError:
|
||||||
|
if not self._closing:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if self._read_fut is not None:
|
||||||
|
self._read_fut.add_done_callback(self._loop_reading)
|
||||||
|
finally:
|
||||||
|
if data:
|
||||||
|
self._protocol.datagram_received(data, addr)
|
||||||
|
|
||||||
|
|
||||||
class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
|
class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
|
||||||
_ProactorBaseWritePipeTransport,
|
_ProactorBaseWritePipeTransport,
|
||||||
transports.Transport):
|
transports.Transport):
|
||||||
@@ -349,21 +606,15 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport,
|
|||||||
transports.Transport):
|
transports.Transport):
|
||||||
"""Transport for connected sockets."""
|
"""Transport for connected sockets."""
|
||||||
|
|
||||||
|
_sendfile_compatible = constants._SendfileMode.TRY_NATIVE
|
||||||
|
|
||||||
|
def __init__(self, loop, sock, protocol, waiter=None,
|
||||||
|
extra=None, server=None):
|
||||||
|
super().__init__(loop, sock, protocol, waiter, extra, server)
|
||||||
|
base_events._set_nodelay(sock)
|
||||||
|
|
||||||
def _set_extra(self, sock):
|
def _set_extra(self, sock):
|
||||||
self._extra['socket'] = sock
|
_set_socket_extra(self, sock)
|
||||||
try:
|
|
||||||
self._extra['sockname'] = sock.getsockname()
|
|
||||||
except (socket.error, AttributeError):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.warning("getsockname() failed on %r",
|
|
||||||
sock, exc_info=True)
|
|
||||||
if 'peername' not in self._extra:
|
|
||||||
try:
|
|
||||||
self._extra['peername'] = sock.getpeername()
|
|
||||||
except (socket.error, AttributeError):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.warning("getpeername() failed on %r",
|
|
||||||
sock, exc_info=True)
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
def can_write_eof(self):
|
||||||
return True
|
return True
|
||||||
@@ -387,26 +638,35 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
self._accept_futures = {} # socket file descriptor => Future
|
self._accept_futures = {} # socket file descriptor => Future
|
||||||
proactor.set_loop(self)
|
proactor.set_loop(self)
|
||||||
self._make_self_pipe()
|
self._make_self_pipe()
|
||||||
|
if threading.current_thread() is threading.main_thread():
|
||||||
|
# wakeup fd can only be installed to a file descriptor from the main thread
|
||||||
|
signal.set_wakeup_fd(self._csock.fileno())
|
||||||
|
|
||||||
def _make_socket_transport(self, sock, protocol, waiter=None,
|
def _make_socket_transport(self, sock, protocol, waiter=None,
|
||||||
extra=None, server=None):
|
extra=None, server=None):
|
||||||
return _ProactorSocketTransport(self, sock, protocol, waiter,
|
return _ProactorSocketTransport(self, sock, protocol, waiter,
|
||||||
extra, server)
|
extra, server)
|
||||||
|
|
||||||
def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None,
|
def _make_ssl_transport(
|
||||||
*, server_side=False, server_hostname=None,
|
self, rawsock, protocol, sslcontext, waiter=None,
|
||||||
extra=None, server=None):
|
*, server_side=False, server_hostname=None,
|
||||||
if not sslproto._is_sslproto_available():
|
extra=None, server=None,
|
||||||
raise NotImplementedError("Proactor event loop requires Python 3.5"
|
ssl_handshake_timeout=None,
|
||||||
" or newer (ssl.MemoryBIO) to support "
|
ssl_shutdown_timeout=None):
|
||||||
"SSL")
|
ssl_protocol = sslproto.SSLProtocol(
|
||||||
|
self, protocol, sslcontext, waiter,
|
||||||
ssl_protocol = sslproto.SSLProtocol(self, protocol, sslcontext, waiter,
|
server_side, server_hostname,
|
||||||
server_side, server_hostname)
|
ssl_handshake_timeout=ssl_handshake_timeout,
|
||||||
|
ssl_shutdown_timeout=ssl_shutdown_timeout)
|
||||||
_ProactorSocketTransport(self, rawsock, ssl_protocol,
|
_ProactorSocketTransport(self, rawsock, ssl_protocol,
|
||||||
extra=extra, server=server)
|
extra=extra, server=server)
|
||||||
return ssl_protocol._app_transport
|
return ssl_protocol._app_transport
|
||||||
|
|
||||||
|
def _make_datagram_transport(self, sock, protocol,
|
||||||
|
address=None, waiter=None, extra=None):
|
||||||
|
return _ProactorDatagramTransport(self, sock, protocol, address,
|
||||||
|
waiter, extra)
|
||||||
|
|
||||||
def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
|
def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
|
||||||
extra=None):
|
extra=None):
|
||||||
return _ProactorDuplexPipeTransport(self,
|
return _ProactorDuplexPipeTransport(self,
|
||||||
@@ -428,6 +688,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
if self.is_closed():
|
if self.is_closed():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if threading.current_thread() is threading.main_thread():
|
||||||
|
signal.set_wakeup_fd(-1)
|
||||||
# Call these methods before closing the event loop (before calling
|
# Call these methods before closing the event loop (before calling
|
||||||
# BaseEventLoop.close), because they can schedule callbacks with
|
# BaseEventLoop.close), because they can schedule callbacks with
|
||||||
# call_soon(), which is forbidden when the event loop is closed.
|
# call_soon(), which is forbidden when the event loop is closed.
|
||||||
@@ -440,20 +702,73 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
# Close the event loop
|
# Close the event loop
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
def sock_recv(self, sock, n):
|
async def sock_recv(self, sock, n):
|
||||||
return self._proactor.recv(sock, n)
|
return await self._proactor.recv(sock, n)
|
||||||
|
|
||||||
def sock_sendall(self, sock, data):
|
async def sock_recv_into(self, sock, buf):
|
||||||
return self._proactor.send(sock, data)
|
return await self._proactor.recv_into(sock, buf)
|
||||||
|
|
||||||
def sock_connect(self, sock, address):
|
async def sock_recvfrom(self, sock, bufsize):
|
||||||
return self._proactor.connect(sock, address)
|
return await self._proactor.recvfrom(sock, bufsize)
|
||||||
|
|
||||||
def sock_accept(self, sock):
|
async def sock_recvfrom_into(self, sock, buf, nbytes=0):
|
||||||
return self._proactor.accept(sock)
|
if not nbytes:
|
||||||
|
nbytes = len(buf)
|
||||||
|
|
||||||
def _socketpair(self):
|
return await self._proactor.recvfrom_into(sock, buf, nbytes)
|
||||||
raise NotImplementedError
|
|
||||||
|
async def sock_sendall(self, sock, data):
|
||||||
|
return await self._proactor.send(sock, data)
|
||||||
|
|
||||||
|
async def sock_sendto(self, sock, data, address):
|
||||||
|
return await self._proactor.sendto(sock, data, 0, address)
|
||||||
|
|
||||||
|
async def sock_connect(self, sock, address):
|
||||||
|
return await self._proactor.connect(sock, address)
|
||||||
|
|
||||||
|
async def sock_accept(self, sock):
|
||||||
|
return await self._proactor.accept(sock)
|
||||||
|
|
||||||
|
async def _sock_sendfile_native(self, sock, file, offset, count):
|
||||||
|
try:
|
||||||
|
fileno = file.fileno()
|
||||||
|
except (AttributeError, io.UnsupportedOperation) as err:
|
||||||
|
raise exceptions.SendfileNotAvailableError("not a regular file")
|
||||||
|
try:
|
||||||
|
fsize = os.fstat(fileno).st_size
|
||||||
|
except OSError:
|
||||||
|
raise exceptions.SendfileNotAvailableError("not a regular file")
|
||||||
|
blocksize = count if count else fsize
|
||||||
|
if not blocksize:
|
||||||
|
return 0 # empty file
|
||||||
|
|
||||||
|
blocksize = min(blocksize, 0xffff_ffff)
|
||||||
|
end_pos = min(offset + count, fsize) if count else fsize
|
||||||
|
offset = min(offset, fsize)
|
||||||
|
total_sent = 0
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
blocksize = min(end_pos - offset, blocksize)
|
||||||
|
if blocksize <= 0:
|
||||||
|
return total_sent
|
||||||
|
await self._proactor.sendfile(sock, file, offset, blocksize)
|
||||||
|
offset += blocksize
|
||||||
|
total_sent += blocksize
|
||||||
|
finally:
|
||||||
|
if total_sent > 0:
|
||||||
|
file.seek(offset)
|
||||||
|
|
||||||
|
async def _sendfile_native(self, transp, file, offset, count):
|
||||||
|
resume_reading = transp.is_reading()
|
||||||
|
transp.pause_reading()
|
||||||
|
await transp._make_empty_waiter()
|
||||||
|
try:
|
||||||
|
return await self.sock_sendfile(transp._sock, file, offset, count,
|
||||||
|
fallback=False)
|
||||||
|
finally:
|
||||||
|
transp._reset_empty_waiter()
|
||||||
|
if resume_reading:
|
||||||
|
transp.resume_reading()
|
||||||
|
|
||||||
def _close_self_pipe(self):
|
def _close_self_pipe(self):
|
||||||
if self._self_reading_future is not None:
|
if self._self_reading_future is not None:
|
||||||
@@ -467,21 +782,30 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
|
|
||||||
def _make_self_pipe(self):
|
def _make_self_pipe(self):
|
||||||
# A self-socket, really. :-)
|
# A self-socket, really. :-)
|
||||||
self._ssock, self._csock = self._socketpair()
|
self._ssock, self._csock = socket.socketpair()
|
||||||
self._ssock.setblocking(False)
|
self._ssock.setblocking(False)
|
||||||
self._csock.setblocking(False)
|
self._csock.setblocking(False)
|
||||||
self._internal_fds += 1
|
self._internal_fds += 1
|
||||||
self.call_soon(self._loop_self_reading)
|
|
||||||
|
|
||||||
def _loop_self_reading(self, f=None):
|
def _loop_self_reading(self, f=None):
|
||||||
try:
|
try:
|
||||||
if f is not None:
|
if f is not None:
|
||||||
f.result() # may raise
|
f.result() # may raise
|
||||||
|
if self._self_reading_future is not f:
|
||||||
|
# When we scheduled this Future, we assigned it to
|
||||||
|
# _self_reading_future. If it's not there now, something has
|
||||||
|
# tried to cancel the loop while this callback was still in the
|
||||||
|
# queue (see windows_events.ProactorEventLoop.run_forever). In
|
||||||
|
# that case stop here instead of continuing to schedule a new
|
||||||
|
# iteration.
|
||||||
|
return
|
||||||
f = self._proactor.recv(self._ssock, 4096)
|
f = self._proactor.recv(self._ssock, 4096)
|
||||||
except futures.CancelledError:
|
except exceptions.CancelledError:
|
||||||
# _close_self_pipe() has been called, stop waiting for data
|
# _close_self_pipe() has been called, stop waiting for data
|
||||||
return
|
return
|
||||||
except Exception as exc:
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
self.call_exception_handler({
|
self.call_exception_handler({
|
||||||
'message': 'Error on reading from the event loop self pipe',
|
'message': 'Error on reading from the event loop self pipe',
|
||||||
'exception': exc,
|
'exception': exc,
|
||||||
@@ -492,10 +816,27 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
f.add_done_callback(self._loop_self_reading)
|
f.add_done_callback(self._loop_self_reading)
|
||||||
|
|
||||||
def _write_to_self(self):
|
def _write_to_self(self):
|
||||||
self._csock.send(b'\0')
|
# This may be called from a different thread, possibly after
|
||||||
|
# _close_self_pipe() has been called or even while it is
|
||||||
|
# running. Guard for self._csock being None or closed. When
|
||||||
|
# a socket is closed, send() raises OSError (with errno set to
|
||||||
|
# EBADF, but let's not rely on the exact error code).
|
||||||
|
csock = self._csock
|
||||||
|
if csock is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
csock.send(b'\0')
|
||||||
|
except OSError:
|
||||||
|
if self._debug:
|
||||||
|
logger.debug("Fail to write a null byte into the "
|
||||||
|
"self-pipe socket",
|
||||||
|
exc_info=True)
|
||||||
|
|
||||||
def _start_serving(self, protocol_factory, sock,
|
def _start_serving(self, protocol_factory, sock,
|
||||||
sslcontext=None, server=None, backlog=100):
|
sslcontext=None, server=None, backlog=100,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None):
|
||||||
|
|
||||||
def loop(f=None):
|
def loop(f=None):
|
||||||
try:
|
try:
|
||||||
@@ -508,7 +849,9 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
if sslcontext is not None:
|
if sslcontext is not None:
|
||||||
self._make_ssl_transport(
|
self._make_ssl_transport(
|
||||||
conn, protocol, sslcontext, server_side=True,
|
conn, protocol, sslcontext, server_side=True,
|
||||||
extra={'peername': addr}, server=server)
|
extra={'peername': addr}, server=server,
|
||||||
|
ssl_handshake_timeout=ssl_handshake_timeout,
|
||||||
|
ssl_shutdown_timeout=ssl_shutdown_timeout)
|
||||||
else:
|
else:
|
||||||
self._make_socket_transport(
|
self._make_socket_transport(
|
||||||
conn, protocol,
|
conn, protocol,
|
||||||
@@ -521,13 +864,13 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
self.call_exception_handler({
|
self.call_exception_handler({
|
||||||
'message': 'Accept failed on a socket',
|
'message': 'Accept failed on a socket',
|
||||||
'exception': exc,
|
'exception': exc,
|
||||||
'socket': sock,
|
'socket': trsock.TransportSocket(sock),
|
||||||
})
|
})
|
||||||
sock.close()
|
sock.close()
|
||||||
elif self._debug:
|
elif self._debug:
|
||||||
logger.debug("Accept failed on socket %r",
|
logger.debug("Accept failed on socket %r",
|
||||||
sock, exc_info=True)
|
sock, exc_info=True)
|
||||||
except futures.CancelledError:
|
except exceptions.CancelledError:
|
||||||
sock.close()
|
sock.close()
|
||||||
else:
|
else:
|
||||||
self._accept_futures[sock.fileno()] = f
|
self._accept_futures[sock.fileno()] = f
|
||||||
@@ -545,6 +888,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||||||
self._accept_futures.clear()
|
self._accept_futures.clear()
|
||||||
|
|
||||||
def _stop_serving(self, sock):
|
def _stop_serving(self, sock):
|
||||||
self._stop_accept_futures()
|
future = self._accept_futures.pop(sock.fileno(), None)
|
||||||
|
if future:
|
||||||
|
future.cancel()
|
||||||
self._proactor._stop_serving(sock)
|
self._proactor._stop_serving(sock)
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|||||||
88
Lib/asyncio/protocols.py
vendored
88
Lib/asyncio/protocols.py
vendored
@@ -1,7 +1,9 @@
|
|||||||
"""Abstract Protocol class."""
|
"""Abstract Protocol base classes."""
|
||||||
|
|
||||||
__all__ = ['BaseProtocol', 'Protocol', 'DatagramProtocol',
|
__all__ = (
|
||||||
'SubprocessProtocol']
|
'BaseProtocol', 'Protocol', 'DatagramProtocol',
|
||||||
|
'SubprocessProtocol', 'BufferedProtocol',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseProtocol:
|
class BaseProtocol:
|
||||||
@@ -14,6 +16,8 @@ class BaseProtocol:
|
|||||||
write-only transport like write pipe
|
write-only transport like write pipe
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport):
|
||||||
"""Called when a connection is made.
|
"""Called when a connection is made.
|
||||||
|
|
||||||
@@ -85,6 +89,8 @@ class Protocol(BaseProtocol):
|
|||||||
* CL: connection_lost()
|
* CL: connection_lost()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
"""Called when some data is received.
|
"""Called when some data is received.
|
||||||
|
|
||||||
@@ -100,9 +106,64 @@ class Protocol(BaseProtocol):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class BufferedProtocol(BaseProtocol):
|
||||||
|
"""Interface for stream protocol with manual buffer control.
|
||||||
|
|
||||||
|
Event methods, such as `create_server` and `create_connection`,
|
||||||
|
accept factories that return protocols that implement this interface.
|
||||||
|
|
||||||
|
The idea of BufferedProtocol is that it allows to manually allocate
|
||||||
|
and control the receive buffer. Event loops can then use the buffer
|
||||||
|
provided by the protocol to avoid unnecessary data copies. This
|
||||||
|
can result in noticeable performance improvement for protocols that
|
||||||
|
receive big amounts of data. Sophisticated protocols can allocate
|
||||||
|
the buffer only once at creation time.
|
||||||
|
|
||||||
|
State machine of calls:
|
||||||
|
|
||||||
|
start -> CM [-> GB [-> BU?]]* [-> ER?] -> CL -> end
|
||||||
|
|
||||||
|
* CM: connection_made()
|
||||||
|
* GB: get_buffer()
|
||||||
|
* BU: buffer_updated()
|
||||||
|
* ER: eof_received()
|
||||||
|
* CL: connection_lost()
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def get_buffer(self, sizehint):
|
||||||
|
"""Called to allocate a new receive buffer.
|
||||||
|
|
||||||
|
*sizehint* is a recommended minimal size for the returned
|
||||||
|
buffer. When set to -1, the buffer size can be arbitrary.
|
||||||
|
|
||||||
|
Must return an object that implements the
|
||||||
|
:ref:`buffer protocol <bufferobjects>`.
|
||||||
|
It is an error to return a zero-sized buffer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def buffer_updated(self, nbytes):
|
||||||
|
"""Called when the buffer was updated with the received data.
|
||||||
|
|
||||||
|
*nbytes* is the total number of bytes that were written to
|
||||||
|
the buffer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def eof_received(self):
|
||||||
|
"""Called when the other end calls write_eof() or equivalent.
|
||||||
|
|
||||||
|
If this returns a false value (including None), the transport
|
||||||
|
will close itself. If it returns a true value, closing the
|
||||||
|
transport is up to the protocol.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class DatagramProtocol(BaseProtocol):
|
class DatagramProtocol(BaseProtocol):
|
||||||
"""Interface for datagram protocol."""
|
"""Interface for datagram protocol."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def datagram_received(self, data, addr):
|
def datagram_received(self, data, addr):
|
||||||
"""Called when some datagram is received."""
|
"""Called when some datagram is received."""
|
||||||
|
|
||||||
@@ -116,6 +177,8 @@ class DatagramProtocol(BaseProtocol):
|
|||||||
class SubprocessProtocol(BaseProtocol):
|
class SubprocessProtocol(BaseProtocol):
|
||||||
"""Interface for protocol for subprocess calls."""
|
"""Interface for protocol for subprocess calls."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def pipe_data_received(self, fd, data):
|
def pipe_data_received(self, fd, data):
|
||||||
"""Called when the subprocess writes data into stdout/stderr pipe.
|
"""Called when the subprocess writes data into stdout/stderr pipe.
|
||||||
|
|
||||||
@@ -132,3 +195,22 @@ class SubprocessProtocol(BaseProtocol):
|
|||||||
|
|
||||||
def process_exited(self):
|
def process_exited(self):
|
||||||
"""Called when subprocess has exited."""
|
"""Called when subprocess has exited."""
|
||||||
|
|
||||||
|
|
||||||
|
def _feed_data_to_buffered_proto(proto, data):
|
||||||
|
data_len = len(data)
|
||||||
|
while data_len:
|
||||||
|
buf = proto.get_buffer(data_len)
|
||||||
|
buf_len = len(buf)
|
||||||
|
if not buf_len:
|
||||||
|
raise RuntimeError('get_buffer() returned an empty buffer')
|
||||||
|
|
||||||
|
if buf_len >= data_len:
|
||||||
|
buf[:data_len] = data
|
||||||
|
proto.buffer_updated(data_len)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
buf[:buf_len] = data[:buf_len]
|
||||||
|
proto.buffer_updated(buf_len)
|
||||||
|
data = data[buf_len:]
|
||||||
|
data_len = len(data)
|
||||||
|
|||||||
90
Lib/asyncio/queues.py
vendored
90
Lib/asyncio/queues.py
vendored
@@ -1,35 +1,28 @@
|
|||||||
"""Queues"""
|
__all__ = ('Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty')
|
||||||
|
|
||||||
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty']
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import heapq
|
import heapq
|
||||||
|
from types import GenericAlias
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import locks
|
from . import locks
|
||||||
from .coroutines import coroutine
|
from . import mixins
|
||||||
|
|
||||||
|
|
||||||
class QueueEmpty(Exception):
|
class QueueEmpty(Exception):
|
||||||
"""Exception raised when Queue.get_nowait() is called on a Queue object
|
"""Raised when Queue.get_nowait() is called on an empty Queue."""
|
||||||
which is empty.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QueueFull(Exception):
|
class QueueFull(Exception):
|
||||||
"""Exception raised when the Queue.put_nowait() method is called on a Queue
|
"""Raised when the Queue.put_nowait() method is called on a full Queue."""
|
||||||
object which is full.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Queue:
|
class Queue(mixins._LoopBoundMixin):
|
||||||
"""A queue, useful for coordinating producer and consumer coroutines.
|
"""A queue, useful for coordinating producer and consumer coroutines.
|
||||||
|
|
||||||
If maxsize is less than or equal to zero, the queue size is infinite. If it
|
If maxsize is less than or equal to zero, the queue size is infinite. If it
|
||||||
is an integer greater than 0, then "yield from put()" will block when the
|
is an integer greater than 0, then "await put()" will block when the
|
||||||
queue reaches maxsize, until an item is removed by get().
|
queue reaches maxsize, until an item is removed by get().
|
||||||
|
|
||||||
Unlike the standard library Queue, you can reliably know this Queue's size
|
Unlike the standard library Queue, you can reliably know this Queue's size
|
||||||
@@ -37,11 +30,7 @@ class Queue:
|
|||||||
interrupted between calling qsize() and doing an operation on the Queue.
|
interrupted between calling qsize() and doing an operation on the Queue.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, maxsize=0, *, loop=None):
|
def __init__(self, maxsize=0):
|
||||||
if loop is None:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
else:
|
|
||||||
self._loop = loop
|
|
||||||
self._maxsize = maxsize
|
self._maxsize = maxsize
|
||||||
|
|
||||||
# Futures.
|
# Futures.
|
||||||
@@ -49,7 +38,7 @@ class Queue:
|
|||||||
# Futures.
|
# Futures.
|
||||||
self._putters = collections.deque()
|
self._putters = collections.deque()
|
||||||
self._unfinished_tasks = 0
|
self._unfinished_tasks = 0
|
||||||
self._finished = locks.Event(loop=self._loop)
|
self._finished = locks.Event()
|
||||||
self._finished.set()
|
self._finished.set()
|
||||||
self._init(maxsize)
|
self._init(maxsize)
|
||||||
|
|
||||||
@@ -75,25 +64,23 @@ class Queue:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{} at {:#x} {}>'.format(
|
return f'<{type(self).__name__} at {id(self):#x} {self._format()}>'
|
||||||
type(self).__name__, id(self), self._format())
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '<{} {}>'.format(type(self).__name__, self._format())
|
return f'<{type(self).__name__} {self._format()}>'
|
||||||
|
|
||||||
def __class_getitem__(cls, type):
|
__class_getitem__ = classmethod(GenericAlias)
|
||||||
return cls
|
|
||||||
|
|
||||||
def _format(self):
|
def _format(self):
|
||||||
result = 'maxsize={!r}'.format(self._maxsize)
|
result = f'maxsize={self._maxsize!r}'
|
||||||
if getattr(self, '_queue', None):
|
if getattr(self, '_queue', None):
|
||||||
result += ' _queue={!r}'.format(list(self._queue))
|
result += f' _queue={list(self._queue)!r}'
|
||||||
if self._getters:
|
if self._getters:
|
||||||
result += ' _getters[{}]'.format(len(self._getters))
|
result += f' _getters[{len(self._getters)}]'
|
||||||
if self._putters:
|
if self._putters:
|
||||||
result += ' _putters[{}]'.format(len(self._putters))
|
result += f' _putters[{len(self._putters)}]'
|
||||||
if self._unfinished_tasks:
|
if self._unfinished_tasks:
|
||||||
result += ' tasks={}'.format(self._unfinished_tasks)
|
result += f' tasks={self._unfinished_tasks}'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def qsize(self):
|
def qsize(self):
|
||||||
@@ -120,22 +107,26 @@ class Queue:
|
|||||||
else:
|
else:
|
||||||
return self.qsize() >= self._maxsize
|
return self.qsize() >= self._maxsize
|
||||||
|
|
||||||
@coroutine
|
async def put(self, item):
|
||||||
def put(self, item):
|
|
||||||
"""Put an item into the queue.
|
"""Put an item into the queue.
|
||||||
|
|
||||||
Put an item into the queue. If the queue is full, wait until a free
|
Put an item into the queue. If the queue is full, wait until a free
|
||||||
slot is available before adding item.
|
slot is available before adding item.
|
||||||
|
|
||||||
This method is a coroutine.
|
|
||||||
"""
|
"""
|
||||||
while self.full():
|
while self.full():
|
||||||
putter = self._loop.create_future()
|
putter = self._get_loop().create_future()
|
||||||
self._putters.append(putter)
|
self._putters.append(putter)
|
||||||
try:
|
try:
|
||||||
yield from putter
|
await putter
|
||||||
except:
|
except:
|
||||||
putter.cancel() # Just in case putter is not done yet.
|
putter.cancel() # Just in case putter is not done yet.
|
||||||
|
try:
|
||||||
|
# Clean self._putters from canceled putters.
|
||||||
|
self._putters.remove(putter)
|
||||||
|
except ValueError:
|
||||||
|
# The putter could be removed from self._putters by a
|
||||||
|
# previous get_nowait call.
|
||||||
|
pass
|
||||||
if not self.full() and not putter.cancelled():
|
if not self.full() and not putter.cancelled():
|
||||||
# We were woken up by get_nowait(), but can't take
|
# We were woken up by get_nowait(), but can't take
|
||||||
# the call. Wake up the next in line.
|
# the call. Wake up the next in line.
|
||||||
@@ -155,21 +146,25 @@ class Queue:
|
|||||||
self._finished.clear()
|
self._finished.clear()
|
||||||
self._wakeup_next(self._getters)
|
self._wakeup_next(self._getters)
|
||||||
|
|
||||||
@coroutine
|
async def get(self):
|
||||||
def get(self):
|
|
||||||
"""Remove and return an item from the queue.
|
"""Remove and return an item from the queue.
|
||||||
|
|
||||||
If queue is empty, wait until an item is available.
|
If queue is empty, wait until an item is available.
|
||||||
|
|
||||||
This method is a coroutine.
|
|
||||||
"""
|
"""
|
||||||
while self.empty():
|
while self.empty():
|
||||||
getter = self._loop.create_future()
|
getter = self._get_loop().create_future()
|
||||||
self._getters.append(getter)
|
self._getters.append(getter)
|
||||||
try:
|
try:
|
||||||
yield from getter
|
await getter
|
||||||
except:
|
except:
|
||||||
getter.cancel() # Just in case getter is not done yet.
|
getter.cancel() # Just in case getter is not done yet.
|
||||||
|
try:
|
||||||
|
# Clean self._getters from canceled getters.
|
||||||
|
self._getters.remove(getter)
|
||||||
|
except ValueError:
|
||||||
|
# The getter could be removed from self._getters by a
|
||||||
|
# previous put_nowait call.
|
||||||
|
pass
|
||||||
if not self.empty() and not getter.cancelled():
|
if not self.empty() and not getter.cancelled():
|
||||||
# We were woken up by put_nowait(), but can't take
|
# We were woken up by put_nowait(), but can't take
|
||||||
# the call. Wake up the next in line.
|
# the call. Wake up the next in line.
|
||||||
@@ -208,8 +203,7 @@ class Queue:
|
|||||||
if self._unfinished_tasks == 0:
|
if self._unfinished_tasks == 0:
|
||||||
self._finished.set()
|
self._finished.set()
|
||||||
|
|
||||||
@coroutine
|
async def join(self):
|
||||||
def join(self):
|
|
||||||
"""Block until all items in the queue have been gotten and processed.
|
"""Block until all items in the queue have been gotten and processed.
|
||||||
|
|
||||||
The count of unfinished tasks goes up whenever an item is added to the
|
The count of unfinished tasks goes up whenever an item is added to the
|
||||||
@@ -218,7 +212,7 @@ class Queue:
|
|||||||
When the count of unfinished tasks drops to zero, join() unblocks.
|
When the count of unfinished tasks drops to zero, join() unblocks.
|
||||||
"""
|
"""
|
||||||
if self._unfinished_tasks > 0:
|
if self._unfinished_tasks > 0:
|
||||||
yield from self._finished.wait()
|
await self._finished.wait()
|
||||||
|
|
||||||
|
|
||||||
class PriorityQueue(Queue):
|
class PriorityQueue(Queue):
|
||||||
@@ -248,9 +242,3 @@ class LifoQueue(Queue):
|
|||||||
|
|
||||||
def _get(self):
|
def _get(self):
|
||||||
return self._queue.pop()
|
return self._queue.pop()
|
||||||
|
|
||||||
|
|
||||||
if not compat.PY35:
|
|
||||||
JoinableQueue = Queue
|
|
||||||
"""Deprecated alias for Queue."""
|
|
||||||
__all__.append('JoinableQueue')
|
|
||||||
|
|||||||
187
Lib/asyncio/runners.py
vendored
187
Lib/asyncio/runners.py
vendored
@@ -1,16 +1,168 @@
|
|||||||
__all__ = ['run']
|
__all__ = ('Runner', 'run')
|
||||||
|
|
||||||
|
import contextvars
|
||||||
|
import enum
|
||||||
|
import functools
|
||||||
|
import threading
|
||||||
|
import signal
|
||||||
from . import coroutines
|
from . import coroutines
|
||||||
from . import events
|
from . import events
|
||||||
|
from . import exceptions
|
||||||
from . import tasks
|
from . import tasks
|
||||||
|
from . import constants
|
||||||
|
|
||||||
|
class _State(enum.Enum):
|
||||||
|
CREATED = "created"
|
||||||
|
INITIALIZED = "initialized"
|
||||||
|
CLOSED = "closed"
|
||||||
|
|
||||||
|
|
||||||
def run(main, *, debug=False):
|
class Runner:
|
||||||
"""Run a coroutine.
|
"""A context manager that controls event loop life cycle.
|
||||||
|
|
||||||
|
The context manager always creates a new event loop,
|
||||||
|
allows to run async functions inside it,
|
||||||
|
and properly finalizes the loop at the context manager exit.
|
||||||
|
|
||||||
|
If debug is True, the event loop will be run in debug mode.
|
||||||
|
If loop_factory is passed, it is used for new event loop creation.
|
||||||
|
|
||||||
|
asyncio.run(main(), debug=True)
|
||||||
|
|
||||||
|
is a shortcut for
|
||||||
|
|
||||||
|
with asyncio.Runner(debug=True) as runner:
|
||||||
|
runner.run(main())
|
||||||
|
|
||||||
|
The run() method can be called multiple times within the runner's context.
|
||||||
|
|
||||||
|
This can be useful for interactive console (e.g. IPython),
|
||||||
|
unittest runners, console tools, -- everywhere when async code
|
||||||
|
is called from existing sync framework and where the preferred single
|
||||||
|
asyncio.run() call doesn't work.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Note: the class is final, it is not intended for inheritance.
|
||||||
|
|
||||||
|
def __init__(self, *, debug=None, loop_factory=None):
|
||||||
|
self._state = _State.CREATED
|
||||||
|
self._debug = debug
|
||||||
|
self._loop_factory = loop_factory
|
||||||
|
self._loop = None
|
||||||
|
self._context = None
|
||||||
|
self._interrupt_count = 0
|
||||||
|
self._set_event_loop = False
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._lazy_init()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Shutdown and close event loop."""
|
||||||
|
if self._state is not _State.INITIALIZED:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
loop = self._loop
|
||||||
|
_cancel_all_tasks(loop)
|
||||||
|
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||||
|
loop.run_until_complete(
|
||||||
|
loop.shutdown_default_executor(constants.THREAD_JOIN_TIMEOUT))
|
||||||
|
finally:
|
||||||
|
if self._set_event_loop:
|
||||||
|
events.set_event_loop(None)
|
||||||
|
loop.close()
|
||||||
|
self._loop = None
|
||||||
|
self._state = _State.CLOSED
|
||||||
|
|
||||||
|
def get_loop(self):
|
||||||
|
"""Return embedded event loop."""
|
||||||
|
self._lazy_init()
|
||||||
|
return self._loop
|
||||||
|
|
||||||
|
def run(self, coro, *, context=None):
|
||||||
|
"""Run a coroutine inside the embedded event loop."""
|
||||||
|
if not coroutines.iscoroutine(coro):
|
||||||
|
raise ValueError("a coroutine was expected, got {!r}".format(coro))
|
||||||
|
|
||||||
|
if events._get_running_loop() is not None:
|
||||||
|
# fail fast with short traceback
|
||||||
|
raise RuntimeError(
|
||||||
|
"Runner.run() cannot be called from a running event loop")
|
||||||
|
|
||||||
|
self._lazy_init()
|
||||||
|
|
||||||
|
if context is None:
|
||||||
|
context = self._context
|
||||||
|
task = self._loop.create_task(coro, context=context)
|
||||||
|
|
||||||
|
if (threading.current_thread() is threading.main_thread()
|
||||||
|
and signal.getsignal(signal.SIGINT) is signal.default_int_handler
|
||||||
|
):
|
||||||
|
sigint_handler = functools.partial(self._on_sigint, main_task=task)
|
||||||
|
try:
|
||||||
|
signal.signal(signal.SIGINT, sigint_handler)
|
||||||
|
except ValueError:
|
||||||
|
# `signal.signal` may throw if `threading.main_thread` does
|
||||||
|
# not support signals (e.g. embedded interpreter with signals
|
||||||
|
# not registered - see gh-91880)
|
||||||
|
sigint_handler = None
|
||||||
|
else:
|
||||||
|
sigint_handler = None
|
||||||
|
|
||||||
|
self._interrupt_count = 0
|
||||||
|
try:
|
||||||
|
return self._loop.run_until_complete(task)
|
||||||
|
except exceptions.CancelledError:
|
||||||
|
if self._interrupt_count > 0:
|
||||||
|
uncancel = getattr(task, "uncancel", None)
|
||||||
|
if uncancel is not None and uncancel() == 0:
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
raise # CancelledError
|
||||||
|
finally:
|
||||||
|
if (sigint_handler is not None
|
||||||
|
and signal.getsignal(signal.SIGINT) is sigint_handler
|
||||||
|
):
|
||||||
|
signal.signal(signal.SIGINT, signal.default_int_handler)
|
||||||
|
|
||||||
|
def _lazy_init(self):
|
||||||
|
if self._state is _State.CLOSED:
|
||||||
|
raise RuntimeError("Runner is closed")
|
||||||
|
if self._state is _State.INITIALIZED:
|
||||||
|
return
|
||||||
|
if self._loop_factory is None:
|
||||||
|
self._loop = events.new_event_loop()
|
||||||
|
if not self._set_event_loop:
|
||||||
|
# Call set_event_loop only once to avoid calling
|
||||||
|
# attach_loop multiple times on child watchers
|
||||||
|
events.set_event_loop(self._loop)
|
||||||
|
self._set_event_loop = True
|
||||||
|
else:
|
||||||
|
self._loop = self._loop_factory()
|
||||||
|
if self._debug is not None:
|
||||||
|
self._loop.set_debug(self._debug)
|
||||||
|
self._context = contextvars.copy_context()
|
||||||
|
self._state = _State.INITIALIZED
|
||||||
|
|
||||||
|
def _on_sigint(self, signum, frame, main_task):
|
||||||
|
self._interrupt_count += 1
|
||||||
|
if self._interrupt_count == 1 and not main_task.done():
|
||||||
|
main_task.cancel()
|
||||||
|
# wakeup loop if it is blocked by select() with long timeout
|
||||||
|
self._loop.call_soon_threadsafe(lambda: None)
|
||||||
|
return
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
|
||||||
|
def run(main, *, debug=None, loop_factory=None):
|
||||||
|
"""Execute the coroutine and return the result.
|
||||||
|
|
||||||
This function runs the passed coroutine, taking care of
|
This function runs the passed coroutine, taking care of
|
||||||
managing the asyncio event loop and finalizing asynchronous
|
managing the asyncio event loop, finalizing asynchronous
|
||||||
generators.
|
generators and closing the default executor.
|
||||||
|
|
||||||
This function cannot be called when another asyncio event loop is
|
This function cannot be called when another asyncio event loop is
|
||||||
running in the same thread.
|
running in the same thread.
|
||||||
@@ -21,6 +173,10 @@ def run(main, *, debug=False):
|
|||||||
It should be used as a main entry point for asyncio programs, and should
|
It should be used as a main entry point for asyncio programs, and should
|
||||||
ideally only be called once.
|
ideally only be called once.
|
||||||
|
|
||||||
|
The executor is given a timeout duration of 5 minutes to shutdown.
|
||||||
|
If the executor hasn't finished within that duration, a warning is
|
||||||
|
emitted and the executor is closed.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
@@ -30,24 +186,12 @@ def run(main, *, debug=False):
|
|||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
"""
|
"""
|
||||||
if events._get_running_loop() is not None:
|
if events._get_running_loop() is not None:
|
||||||
|
# fail fast with short traceback
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"asyncio.run() cannot be called from a running event loop")
|
"asyncio.run() cannot be called from a running event loop")
|
||||||
|
|
||||||
if not coroutines.iscoroutine(main):
|
with Runner(debug=debug, loop_factory=loop_factory) as runner:
|
||||||
raise ValueError("a coroutine was expected, got {!r}".format(main))
|
return runner.run(main)
|
||||||
|
|
||||||
loop = events.new_event_loop()
|
|
||||||
try:
|
|
||||||
events.set_event_loop(loop)
|
|
||||||
loop.set_debug(debug)
|
|
||||||
return loop.run_until_complete(main)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
_cancel_all_tasks(loop)
|
|
||||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
||||||
finally:
|
|
||||||
events.set_event_loop(None)
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _cancel_all_tasks(loop):
|
def _cancel_all_tasks(loop):
|
||||||
@@ -58,8 +202,7 @@ def _cancel_all_tasks(loop):
|
|||||||
for task in to_cancel:
|
for task in to_cancel:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
loop.run_until_complete(
|
loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
|
||||||
tasks.gather(*to_cancel, loop=loop, return_exceptions=True))
|
|
||||||
|
|
||||||
for task in to_cancel:
|
for task in to_cancel:
|
||||||
if task.cancelled():
|
if task.cancelled():
|
||||||
|
|||||||
1091
Lib/asyncio/selector_events.py
vendored
1091
Lib/asyncio/selector_events.py
vendored
File diff suppressed because it is too large
Load Diff
1136
Lib/asyncio/sslproto.py
vendored
1136
Lib/asyncio/sslproto.py
vendored
File diff suppressed because it is too large
Load Diff
149
Lib/asyncio/staggered.py
vendored
Normal file
149
Lib/asyncio/staggered.py
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
"""Support for running coroutines in parallel with staggered start times."""
|
||||||
|
|
||||||
|
__all__ = 'staggered_race',
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from . import events
|
||||||
|
from . import exceptions as exceptions_mod
|
||||||
|
from . import locks
|
||||||
|
from . import tasks
|
||||||
|
|
||||||
|
|
||||||
|
async def staggered_race(
|
||||||
|
coro_fns: typing.Iterable[typing.Callable[[], typing.Awaitable]],
|
||||||
|
delay: typing.Optional[float],
|
||||||
|
*,
|
||||||
|
loop: events.AbstractEventLoop = None,
|
||||||
|
) -> typing.Tuple[
|
||||||
|
typing.Any,
|
||||||
|
typing.Optional[int],
|
||||||
|
typing.List[typing.Optional[Exception]]
|
||||||
|
]:
|
||||||
|
"""Run coroutines with staggered start times and take the first to finish.
|
||||||
|
|
||||||
|
This method takes an iterable of coroutine functions. The first one is
|
||||||
|
started immediately. From then on, whenever the immediately preceding one
|
||||||
|
fails (raises an exception), or when *delay* seconds has passed, the next
|
||||||
|
coroutine is started. This continues until one of the coroutines complete
|
||||||
|
successfully, in which case all others are cancelled, or until all
|
||||||
|
coroutines fail.
|
||||||
|
|
||||||
|
The coroutines provided should be well-behaved in the following way:
|
||||||
|
|
||||||
|
* They should only ``return`` if completed successfully.
|
||||||
|
|
||||||
|
* They should always raise an exception if they did not complete
|
||||||
|
successfully. In particular, if they handle cancellation, they should
|
||||||
|
probably reraise, like this::
|
||||||
|
|
||||||
|
try:
|
||||||
|
# do work
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# undo partially completed work
|
||||||
|
raise
|
||||||
|
|
||||||
|
Args:
|
||||||
|
coro_fns: an iterable of coroutine functions, i.e. callables that
|
||||||
|
return a coroutine object when called. Use ``functools.partial`` or
|
||||||
|
lambdas to pass arguments.
|
||||||
|
|
||||||
|
delay: amount of time, in seconds, between starting coroutines. If
|
||||||
|
``None``, the coroutines will run sequentially.
|
||||||
|
|
||||||
|
loop: the event loop to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple *(winner_result, winner_index, exceptions)* where
|
||||||
|
|
||||||
|
- *winner_result*: the result of the winning coroutine, or ``None``
|
||||||
|
if no coroutines won.
|
||||||
|
|
||||||
|
- *winner_index*: the index of the winning coroutine in
|
||||||
|
``coro_fns``, or ``None`` if no coroutines won. If the winning
|
||||||
|
coroutine may return None on success, *winner_index* can be used
|
||||||
|
to definitively determine whether any coroutine won.
|
||||||
|
|
||||||
|
- *exceptions*: list of exceptions returned by the coroutines.
|
||||||
|
``len(exceptions)`` is equal to the number of coroutines actually
|
||||||
|
started, and the order is the same as in ``coro_fns``. The winning
|
||||||
|
coroutine's entry is ``None``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: when we have aiter() and anext(), allow async iterables in coro_fns.
|
||||||
|
loop = loop or events.get_running_loop()
|
||||||
|
enum_coro_fns = enumerate(coro_fns)
|
||||||
|
winner_result = None
|
||||||
|
winner_index = None
|
||||||
|
exceptions = []
|
||||||
|
running_tasks = []
|
||||||
|
|
||||||
|
async def run_one_coro(
|
||||||
|
previous_failed: typing.Optional[locks.Event]) -> None:
|
||||||
|
# Wait for the previous task to finish, or for delay seconds
|
||||||
|
if previous_failed is not None:
|
||||||
|
with contextlib.suppress(exceptions_mod.TimeoutError):
|
||||||
|
# Use asyncio.wait_for() instead of asyncio.wait() here, so
|
||||||
|
# that if we get cancelled at this point, Event.wait() is also
|
||||||
|
# cancelled, otherwise there will be a "Task destroyed but it is
|
||||||
|
# pending" later.
|
||||||
|
await tasks.wait_for(previous_failed.wait(), delay)
|
||||||
|
# Get the next coroutine to run
|
||||||
|
try:
|
||||||
|
this_index, coro_fn = next(enum_coro_fns)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
# Start task that will run the next coroutine
|
||||||
|
this_failed = locks.Event()
|
||||||
|
next_task = loop.create_task(run_one_coro(this_failed))
|
||||||
|
running_tasks.append(next_task)
|
||||||
|
assert len(running_tasks) == this_index + 2
|
||||||
|
# Prepare place to put this coroutine's exceptions if not won
|
||||||
|
exceptions.append(None)
|
||||||
|
assert len(exceptions) == this_index + 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await coro_fn()
|
||||||
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as e:
|
||||||
|
exceptions[this_index] = e
|
||||||
|
this_failed.set() # Kickstart the next coroutine
|
||||||
|
else:
|
||||||
|
# Store winner's results
|
||||||
|
nonlocal winner_index, winner_result
|
||||||
|
assert winner_index is None
|
||||||
|
winner_index = this_index
|
||||||
|
winner_result = result
|
||||||
|
# Cancel all other tasks. We take care to not cancel the current
|
||||||
|
# task as well. If we do so, then since there is no `await` after
|
||||||
|
# here and CancelledError are usually thrown at one, we will
|
||||||
|
# encounter a curious corner case where the current task will end
|
||||||
|
# up as done() == True, cancelled() == False, exception() ==
|
||||||
|
# asyncio.CancelledError. This behavior is specified in
|
||||||
|
# https://bugs.python.org/issue30048
|
||||||
|
for i, t in enumerate(running_tasks):
|
||||||
|
if i != this_index:
|
||||||
|
t.cancel()
|
||||||
|
|
||||||
|
first_task = loop.create_task(run_one_coro(None))
|
||||||
|
running_tasks.append(first_task)
|
||||||
|
try:
|
||||||
|
# Wait for a growing list of tasks to all finish: poor man's version of
|
||||||
|
# curio's TaskGroup or trio's nursery
|
||||||
|
done_count = 0
|
||||||
|
while done_count != len(running_tasks):
|
||||||
|
done, _ = await tasks.wait(running_tasks)
|
||||||
|
done_count = len(done)
|
||||||
|
# If run_one_coro raises an unhandled exception, it's probably a
|
||||||
|
# programming error, and I want to see it.
|
||||||
|
if __debug__:
|
||||||
|
for d in done:
|
||||||
|
if d.done() and not d.cancelled() and d.exception():
|
||||||
|
raise d.exception()
|
||||||
|
return winner_result, winner_index, exceptions
|
||||||
|
finally:
|
||||||
|
# Make sure no tasks are left running if we leave this function
|
||||||
|
for t in running_tasks:
|
||||||
|
t.cancel()
|
||||||
411
Lib/asyncio/streams.py
vendored
411
Lib/asyncio/streams.py
vendored
@@ -1,55 +1,30 @@
|
|||||||
"""Stream-related things."""
|
__all__ = (
|
||||||
|
'StreamReader', 'StreamWriter', 'StreamReaderProtocol',
|
||||||
__all__ = ['StreamReader', 'StreamWriter', 'StreamReaderProtocol',
|
'open_connection', 'start_server')
|
||||||
'open_connection', 'start_server',
|
|
||||||
'IncompleteReadError',
|
|
||||||
'LimitOverrunError',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
import collections
|
||||||
import socket
|
import socket
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
import weakref
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
if hasattr(socket, 'AF_UNIX'):
|
||||||
__all__.extend(['open_unix_connection', 'start_unix_server'])
|
__all__ += ('open_unix_connection', 'start_unix_server')
|
||||||
|
|
||||||
from . import coroutines
|
from . import coroutines
|
||||||
from . import compat
|
|
||||||
from . import events
|
from . import events
|
||||||
|
from . import exceptions
|
||||||
|
from . import format_helpers
|
||||||
from . import protocols
|
from . import protocols
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
from .tasks import sleep
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_LIMIT = 2 ** 16
|
_DEFAULT_LIMIT = 2 ** 16 # 64 KiB
|
||||||
|
|
||||||
|
|
||||||
class IncompleteReadError(EOFError):
|
async def open_connection(host=None, port=None, *,
|
||||||
"""
|
limit=_DEFAULT_LIMIT, **kwds):
|
||||||
Incomplete read error. Attributes:
|
|
||||||
|
|
||||||
- partial: read bytes string before the end of stream was reached
|
|
||||||
- expected: total number of expected bytes (or None if unknown)
|
|
||||||
"""
|
|
||||||
def __init__(self, partial, expected):
|
|
||||||
super().__init__("%d bytes read on a total of %r expected bytes"
|
|
||||||
% (len(partial), expected))
|
|
||||||
self.partial = partial
|
|
||||||
self.expected = expected
|
|
||||||
|
|
||||||
|
|
||||||
class LimitOverrunError(Exception):
|
|
||||||
"""Reached the buffer limit while looking for a separator.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
- consumed: total number of to be consumed bytes.
|
|
||||||
"""
|
|
||||||
def __init__(self, message, consumed):
|
|
||||||
super().__init__(message)
|
|
||||||
self.consumed = consumed
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def open_connection(host=None, port=None, *,
|
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""A wrapper for create_connection() returning a (reader, writer) pair.
|
"""A wrapper for create_connection() returning a (reader, writer) pair.
|
||||||
|
|
||||||
The reader returned is a StreamReader instance; the writer is a
|
The reader returned is a StreamReader instance; the writer is a
|
||||||
@@ -67,19 +42,17 @@ def open_connection(host=None, port=None, *,
|
|||||||
StreamReaderProtocol classes, just copy the code -- there's
|
StreamReaderProtocol classes, just copy the code -- there's
|
||||||
really nothing special here except some convenience.)
|
really nothing special here except some convenience.)
|
||||||
"""
|
"""
|
||||||
if loop is None:
|
loop = events.get_running_loop()
|
||||||
loop = events.get_event_loop()
|
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
reader = StreamReader(limit=limit, loop=loop)
|
||||||
protocol = StreamReaderProtocol(reader, loop=loop)
|
protocol = StreamReaderProtocol(reader, loop=loop)
|
||||||
transport, _ = yield from loop.create_connection(
|
transport, _ = await loop.create_connection(
|
||||||
lambda: protocol, host, port, **kwds)
|
lambda: protocol, host, port, **kwds)
|
||||||
writer = StreamWriter(transport, protocol, reader, loop)
|
writer = StreamWriter(transport, protocol, reader, loop)
|
||||||
return reader, writer
|
return reader, writer
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def start_server(client_connected_cb, host=None, port=None, *,
|
||||||
def start_server(client_connected_cb, host=None, port=None, *,
|
limit=_DEFAULT_LIMIT, **kwds):
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""Start a socket server, call back for each client connected.
|
"""Start a socket server, call back for each client connected.
|
||||||
|
|
||||||
The first parameter, `client_connected_cb`, takes two parameters:
|
The first parameter, `client_connected_cb`, takes two parameters:
|
||||||
@@ -94,15 +67,13 @@ def start_server(client_connected_cb, host=None, port=None, *,
|
|||||||
positional host and port, with various optional keyword arguments
|
positional host and port, with various optional keyword arguments
|
||||||
following. The return value is the same as loop.create_server().
|
following. The return value is the same as loop.create_server().
|
||||||
|
|
||||||
Additional optional keyword arguments are loop (to set the event loop
|
Additional optional keyword argument is limit (to set the buffer
|
||||||
instance to use) and limit (to set the buffer limit passed to the
|
limit passed to the StreamReader).
|
||||||
StreamReader).
|
|
||||||
|
|
||||||
The return value is the same as loop.create_server(), i.e. a
|
The return value is the same as loop.create_server(), i.e. a
|
||||||
Server object which can be used to stop the service.
|
Server object which can be used to stop the service.
|
||||||
"""
|
"""
|
||||||
if loop is None:
|
loop = events.get_running_loop()
|
||||||
loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def factory():
|
def factory():
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
reader = StreamReader(limit=limit, loop=loop)
|
||||||
@@ -110,31 +81,28 @@ def start_server(client_connected_cb, host=None, port=None, *,
|
|||||||
loop=loop)
|
loop=loop)
|
||||||
return protocol
|
return protocol
|
||||||
|
|
||||||
return (yield from loop.create_server(factory, host, port, **kwds))
|
return await loop.create_server(factory, host, port, **kwds)
|
||||||
|
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
if hasattr(socket, 'AF_UNIX'):
|
||||||
# UNIX Domain Sockets are supported on this platform
|
# UNIX Domain Sockets are supported on this platform
|
||||||
|
|
||||||
@coroutine
|
async def open_unix_connection(path=None, *,
|
||||||
def open_unix_connection(path=None, *,
|
limit=_DEFAULT_LIMIT, **kwds):
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""Similar to `open_connection` but works with UNIX Domain Sockets."""
|
"""Similar to `open_connection` but works with UNIX Domain Sockets."""
|
||||||
if loop is None:
|
loop = events.get_running_loop()
|
||||||
loop = events.get_event_loop()
|
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
reader = StreamReader(limit=limit, loop=loop)
|
||||||
protocol = StreamReaderProtocol(reader, loop=loop)
|
protocol = StreamReaderProtocol(reader, loop=loop)
|
||||||
transport, _ = yield from loop.create_unix_connection(
|
transport, _ = await loop.create_unix_connection(
|
||||||
lambda: protocol, path, **kwds)
|
lambda: protocol, path, **kwds)
|
||||||
writer = StreamWriter(transport, protocol, reader, loop)
|
writer = StreamWriter(transport, protocol, reader, loop)
|
||||||
return reader, writer
|
return reader, writer
|
||||||
|
|
||||||
@coroutine
|
async def start_unix_server(client_connected_cb, path=None, *,
|
||||||
def start_unix_server(client_connected_cb, path=None, *,
|
limit=_DEFAULT_LIMIT, **kwds):
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""Similar to `start_server` but works with UNIX Domain Sockets."""
|
"""Similar to `start_server` but works with UNIX Domain Sockets."""
|
||||||
if loop is None:
|
loop = events.get_running_loop()
|
||||||
loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def factory():
|
def factory():
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
reader = StreamReader(limit=limit, loop=loop)
|
||||||
@@ -142,14 +110,14 @@ if hasattr(socket, 'AF_UNIX'):
|
|||||||
loop=loop)
|
loop=loop)
|
||||||
return protocol
|
return protocol
|
||||||
|
|
||||||
return (yield from loop.create_unix_server(factory, path, **kwds))
|
return await loop.create_unix_server(factory, path, **kwds)
|
||||||
|
|
||||||
|
|
||||||
class FlowControlMixin(protocols.Protocol):
|
class FlowControlMixin(protocols.Protocol):
|
||||||
"""Reusable flow control logic for StreamWriter.drain().
|
"""Reusable flow control logic for StreamWriter.drain().
|
||||||
|
|
||||||
This implements the protocol methods pause_writing(),
|
This implements the protocol methods pause_writing(),
|
||||||
resume_reading() and connection_lost(). If the subclass overrides
|
resume_writing() and connection_lost(). If the subclass overrides
|
||||||
these it must call the super methods.
|
these it must call the super methods.
|
||||||
|
|
||||||
StreamWriter.drain() must wait for _drain_helper() coroutine.
|
StreamWriter.drain() must wait for _drain_helper() coroutine.
|
||||||
@@ -161,7 +129,7 @@ class FlowControlMixin(protocols.Protocol):
|
|||||||
else:
|
else:
|
||||||
self._loop = loop
|
self._loop = loop
|
||||||
self._paused = False
|
self._paused = False
|
||||||
self._drain_waiter = None
|
self._drain_waiters = collections.deque()
|
||||||
self._connection_lost = False
|
self._connection_lost = False
|
||||||
|
|
||||||
def pause_writing(self):
|
def pause_writing(self):
|
||||||
@@ -176,39 +144,37 @@ class FlowControlMixin(protocols.Protocol):
|
|||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
logger.debug("%r resumes writing", self)
|
logger.debug("%r resumes writing", self)
|
||||||
|
|
||||||
waiter = self._drain_waiter
|
for waiter in self._drain_waiters:
|
||||||
if waiter is not None:
|
|
||||||
self._drain_waiter = None
|
|
||||||
if not waiter.done():
|
if not waiter.done():
|
||||||
waiter.set_result(None)
|
waiter.set_result(None)
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
self._connection_lost = True
|
self._connection_lost = True
|
||||||
# Wake up the writer if currently paused.
|
# Wake up the writer(s) if currently paused.
|
||||||
if not self._paused:
|
if not self._paused:
|
||||||
return
|
return
|
||||||
waiter = self._drain_waiter
|
|
||||||
if waiter is None:
|
|
||||||
return
|
|
||||||
self._drain_waiter = None
|
|
||||||
if waiter.done():
|
|
||||||
return
|
|
||||||
if exc is None:
|
|
||||||
waiter.set_result(None)
|
|
||||||
else:
|
|
||||||
waiter.set_exception(exc)
|
|
||||||
|
|
||||||
@coroutine
|
for waiter in self._drain_waiters:
|
||||||
def _drain_helper(self):
|
if not waiter.done():
|
||||||
|
if exc is None:
|
||||||
|
waiter.set_result(None)
|
||||||
|
else:
|
||||||
|
waiter.set_exception(exc)
|
||||||
|
|
||||||
|
async def _drain_helper(self):
|
||||||
if self._connection_lost:
|
if self._connection_lost:
|
||||||
raise ConnectionResetError('Connection lost')
|
raise ConnectionResetError('Connection lost')
|
||||||
if not self._paused:
|
if not self._paused:
|
||||||
return
|
return
|
||||||
waiter = self._drain_waiter
|
|
||||||
assert waiter is None or waiter.cancelled()
|
|
||||||
waiter = self._loop.create_future()
|
waiter = self._loop.create_future()
|
||||||
self._drain_waiter = waiter
|
self._drain_waiters.append(waiter)
|
||||||
yield from waiter
|
try:
|
||||||
|
await waiter
|
||||||
|
finally:
|
||||||
|
self._drain_waiters.remove(waiter)
|
||||||
|
|
||||||
|
def _get_close_waiter(self, stream):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
||||||
@@ -220,40 +186,110 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
|||||||
call inappropriate methods of the protocol.)
|
call inappropriate methods of the protocol.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_source_traceback = None
|
||||||
|
|
||||||
def __init__(self, stream_reader, client_connected_cb=None, loop=None):
|
def __init__(self, stream_reader, client_connected_cb=None, loop=None):
|
||||||
super().__init__(loop=loop)
|
super().__init__(loop=loop)
|
||||||
self._stream_reader = stream_reader
|
if stream_reader is not None:
|
||||||
|
self._stream_reader_wr = weakref.ref(stream_reader)
|
||||||
|
self._source_traceback = stream_reader._source_traceback
|
||||||
|
else:
|
||||||
|
self._stream_reader_wr = None
|
||||||
|
if client_connected_cb is not None:
|
||||||
|
# This is a stream created by the `create_server()` function.
|
||||||
|
# Keep a strong reference to the reader until a connection
|
||||||
|
# is established.
|
||||||
|
self._strong_reader = stream_reader
|
||||||
|
self._reject_connection = False
|
||||||
self._stream_writer = None
|
self._stream_writer = None
|
||||||
|
self._task = None
|
||||||
|
self._transport = None
|
||||||
self._client_connected_cb = client_connected_cb
|
self._client_connected_cb = client_connected_cb
|
||||||
self._over_ssl = False
|
self._over_ssl = False
|
||||||
|
self._closed = self._loop.create_future()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _stream_reader(self):
|
||||||
|
if self._stream_reader_wr is None:
|
||||||
|
return None
|
||||||
|
return self._stream_reader_wr()
|
||||||
|
|
||||||
|
def _replace_writer(self, writer):
|
||||||
|
loop = self._loop
|
||||||
|
transport = writer.transport
|
||||||
|
self._stream_writer = writer
|
||||||
|
self._transport = transport
|
||||||
|
self._over_ssl = transport.get_extra_info('sslcontext') is not None
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport):
|
||||||
self._stream_reader.set_transport(transport)
|
if self._reject_connection:
|
||||||
|
context = {
|
||||||
|
'message': ('An open stream was garbage collected prior to '
|
||||||
|
'establishing network connection; '
|
||||||
|
'call "stream.close()" explicitly.')
|
||||||
|
}
|
||||||
|
if self._source_traceback:
|
||||||
|
context['source_traceback'] = self._source_traceback
|
||||||
|
self._loop.call_exception_handler(context)
|
||||||
|
transport.abort()
|
||||||
|
return
|
||||||
|
self._transport = transport
|
||||||
|
reader = self._stream_reader
|
||||||
|
if reader is not None:
|
||||||
|
reader.set_transport(transport)
|
||||||
self._over_ssl = transport.get_extra_info('sslcontext') is not None
|
self._over_ssl = transport.get_extra_info('sslcontext') is not None
|
||||||
if self._client_connected_cb is not None:
|
if self._client_connected_cb is not None:
|
||||||
self._stream_writer = StreamWriter(transport, self,
|
self._stream_writer = StreamWriter(transport, self,
|
||||||
self._stream_reader,
|
reader,
|
||||||
self._loop)
|
self._loop)
|
||||||
res = self._client_connected_cb(self._stream_reader,
|
res = self._client_connected_cb(reader,
|
||||||
self._stream_writer)
|
self._stream_writer)
|
||||||
if coroutines.iscoroutine(res):
|
if coroutines.iscoroutine(res):
|
||||||
self._loop.create_task(res)
|
def callback(task):
|
||||||
|
if task.cancelled():
|
||||||
|
transport.close()
|
||||||
|
return
|
||||||
|
exc = task.exception()
|
||||||
|
if exc is not None:
|
||||||
|
self._loop.call_exception_handler({
|
||||||
|
'message': 'Unhandled exception in client_connected_cb',
|
||||||
|
'exception': exc,
|
||||||
|
'transport': transport,
|
||||||
|
})
|
||||||
|
transport.close()
|
||||||
|
|
||||||
|
self._task = self._loop.create_task(res)
|
||||||
|
self._task.add_done_callback(callback)
|
||||||
|
|
||||||
|
self._strong_reader = None
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
if self._stream_reader is not None:
|
reader = self._stream_reader
|
||||||
|
if reader is not None:
|
||||||
if exc is None:
|
if exc is None:
|
||||||
self._stream_reader.feed_eof()
|
reader.feed_eof()
|
||||||
else:
|
else:
|
||||||
self._stream_reader.set_exception(exc)
|
reader.set_exception(exc)
|
||||||
|
if not self._closed.done():
|
||||||
|
if exc is None:
|
||||||
|
self._closed.set_result(None)
|
||||||
|
else:
|
||||||
|
self._closed.set_exception(exc)
|
||||||
super().connection_lost(exc)
|
super().connection_lost(exc)
|
||||||
self._stream_reader = None
|
self._stream_reader_wr = None
|
||||||
self._stream_writer = None
|
self._stream_writer = None
|
||||||
|
self._task = None
|
||||||
|
self._transport = None
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
self._stream_reader.feed_data(data)
|
reader = self._stream_reader
|
||||||
|
if reader is not None:
|
||||||
|
reader.feed_data(data)
|
||||||
|
|
||||||
def eof_received(self):
|
def eof_received(self):
|
||||||
self._stream_reader.feed_eof()
|
reader = self._stream_reader
|
||||||
|
if reader is not None:
|
||||||
|
reader.feed_eof()
|
||||||
if self._over_ssl:
|
if self._over_ssl:
|
||||||
# Prevent a warning in SSLProtocol.eof_received:
|
# Prevent a warning in SSLProtocol.eof_received:
|
||||||
# "returning true from eof_received()
|
# "returning true from eof_received()
|
||||||
@@ -261,6 +297,20 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _get_close_waiter(self, stream):
|
||||||
|
return self._closed
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
# Prevent reports about unhandled exceptions.
|
||||||
|
# Better than self._closed._log_traceback = False hack
|
||||||
|
try:
|
||||||
|
closed = self._closed
|
||||||
|
except AttributeError:
|
||||||
|
pass # failed constructor
|
||||||
|
else:
|
||||||
|
if closed.done() and not closed.cancelled():
|
||||||
|
closed.exception()
|
||||||
|
|
||||||
|
|
||||||
class StreamWriter:
|
class StreamWriter:
|
||||||
"""Wraps a Transport.
|
"""Wraps a Transport.
|
||||||
@@ -279,12 +329,14 @@ class StreamWriter:
|
|||||||
assert reader is None or isinstance(reader, StreamReader)
|
assert reader is None or isinstance(reader, StreamReader)
|
||||||
self._reader = reader
|
self._reader = reader
|
||||||
self._loop = loop
|
self._loop = loop
|
||||||
|
self._complete_fut = self._loop.create_future()
|
||||||
|
self._complete_fut.set_result(None)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
info = [self.__class__.__name__, 'transport=%r' % self._transport]
|
info = [self.__class__.__name__, f'transport={self._transport!r}']
|
||||||
if self._reader is not None:
|
if self._reader is not None:
|
||||||
info.append('reader=%r' % self._reader)
|
info.append(f'reader={self._reader!r}')
|
||||||
return '<%s>' % ' '.join(info)
|
return '<{}>'.format(' '.join(info))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def transport(self):
|
def transport(self):
|
||||||
@@ -305,36 +357,68 @@ class StreamWriter:
|
|||||||
def close(self):
|
def close(self):
|
||||||
return self._transport.close()
|
return self._transport.close()
|
||||||
|
|
||||||
|
def is_closing(self):
|
||||||
|
return self._transport.is_closing()
|
||||||
|
|
||||||
|
async def wait_closed(self):
|
||||||
|
await self._protocol._get_close_waiter(self)
|
||||||
|
|
||||||
def get_extra_info(self, name, default=None):
|
def get_extra_info(self, name, default=None):
|
||||||
return self._transport.get_extra_info(name, default)
|
return self._transport.get_extra_info(name, default)
|
||||||
|
|
||||||
@coroutine
|
async def drain(self):
|
||||||
def drain(self):
|
|
||||||
"""Flush the write buffer.
|
"""Flush the write buffer.
|
||||||
|
|
||||||
The intended use is to write
|
The intended use is to write
|
||||||
|
|
||||||
w.write(data)
|
w.write(data)
|
||||||
yield from w.drain()
|
await w.drain()
|
||||||
"""
|
"""
|
||||||
if self._reader is not None:
|
if self._reader is not None:
|
||||||
exc = self._reader.exception()
|
exc = self._reader.exception()
|
||||||
if exc is not None:
|
if exc is not None:
|
||||||
raise exc
|
raise exc
|
||||||
if self._transport is not None:
|
if self._transport.is_closing():
|
||||||
if self._transport.is_closing():
|
# Wait for protocol.connection_lost() call
|
||||||
# Yield to the event loop so connection_lost() may be
|
# Raise connection closing error if any,
|
||||||
# called. Without this, _drain_helper() would return
|
# ConnectionResetError otherwise
|
||||||
# immediately, and code that calls
|
# Yield to the event loop so connection_lost() may be
|
||||||
# write(...); yield from drain()
|
# called. Without this, _drain_helper() would return
|
||||||
# in a loop would never call connection_lost(), so it
|
# immediately, and code that calls
|
||||||
# would not see an error when the socket is closed.
|
# write(...); await drain()
|
||||||
yield
|
# in a loop would never call connection_lost(), so it
|
||||||
yield from self._protocol._drain_helper()
|
# would not see an error when the socket is closed.
|
||||||
|
await sleep(0)
|
||||||
|
await self._protocol._drain_helper()
|
||||||
|
|
||||||
|
async def start_tls(self, sslcontext, *,
|
||||||
|
server_hostname=None,
|
||||||
|
ssl_handshake_timeout=None,
|
||||||
|
ssl_shutdown_timeout=None):
|
||||||
|
"""Upgrade an existing stream-based connection to TLS."""
|
||||||
|
server_side = self._protocol._client_connected_cb is not None
|
||||||
|
protocol = self._protocol
|
||||||
|
await self.drain()
|
||||||
|
new_transport = await self._loop.start_tls( # type: ignore
|
||||||
|
self._transport, protocol, sslcontext,
|
||||||
|
server_side=server_side, server_hostname=server_hostname,
|
||||||
|
ssl_handshake_timeout=ssl_handshake_timeout,
|
||||||
|
ssl_shutdown_timeout=ssl_shutdown_timeout)
|
||||||
|
self._transport = new_transport
|
||||||
|
protocol._replace_writer(self)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if not self._transport.is_closing():
|
||||||
|
if self._loop.is_closed():
|
||||||
|
warnings.warn("loop is closed", ResourceWarning)
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
warnings.warn(f"unclosed {self!r}", ResourceWarning)
|
||||||
|
|
||||||
class StreamReader:
|
class StreamReader:
|
||||||
|
|
||||||
|
_source_traceback = None
|
||||||
|
|
||||||
def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
|
def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
|
||||||
# The line length limit is a security feature;
|
# The line length limit is a security feature;
|
||||||
# it also doubles as half the buffer limit.
|
# it also doubles as half the buffer limit.
|
||||||
@@ -353,24 +437,27 @@ class StreamReader:
|
|||||||
self._exception = None
|
self._exception = None
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._paused = False
|
self._paused = False
|
||||||
|
if self._loop.get_debug():
|
||||||
|
self._source_traceback = format_helpers.extract_stack(
|
||||||
|
sys._getframe(1))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
info = ['StreamReader']
|
info = ['StreamReader']
|
||||||
if self._buffer:
|
if self._buffer:
|
||||||
info.append('%d bytes' % len(self._buffer))
|
info.append(f'{len(self._buffer)} bytes')
|
||||||
if self._eof:
|
if self._eof:
|
||||||
info.append('eof')
|
info.append('eof')
|
||||||
if self._limit != _DEFAULT_LIMIT:
|
if self._limit != _DEFAULT_LIMIT:
|
||||||
info.append('l=%d' % self._limit)
|
info.append(f'limit={self._limit}')
|
||||||
if self._waiter:
|
if self._waiter:
|
||||||
info.append('w=%r' % self._waiter)
|
info.append(f'waiter={self._waiter!r}')
|
||||||
if self._exception:
|
if self._exception:
|
||||||
info.append('e=%r' % self._exception)
|
info.append(f'exception={self._exception!r}')
|
||||||
if self._transport:
|
if self._transport:
|
||||||
info.append('t=%r' % self._transport)
|
info.append(f'transport={self._transport!r}')
|
||||||
if self._paused:
|
if self._paused:
|
||||||
info.append('paused')
|
info.append('paused')
|
||||||
return '<%s>' % ' '.join(info)
|
return '<{}>'.format(' '.join(info))
|
||||||
|
|
||||||
def exception(self):
|
def exception(self):
|
||||||
return self._exception
|
return self._exception
|
||||||
@@ -431,8 +518,7 @@ class StreamReader:
|
|||||||
else:
|
else:
|
||||||
self._paused = True
|
self._paused = True
|
||||||
|
|
||||||
@coroutine
|
async def _wait_for_data(self, func_name):
|
||||||
def _wait_for_data(self, func_name):
|
|
||||||
"""Wait until feed_data() or feed_eof() is called.
|
"""Wait until feed_data() or feed_eof() is called.
|
||||||
|
|
||||||
If stream was paused, automatically resume it.
|
If stream was paused, automatically resume it.
|
||||||
@@ -442,8 +528,9 @@ class StreamReader:
|
|||||||
# would have an unexpected behaviour. It would not possible to know
|
# would have an unexpected behaviour. It would not possible to know
|
||||||
# which coroutine would get the next data.
|
# which coroutine would get the next data.
|
||||||
if self._waiter is not None:
|
if self._waiter is not None:
|
||||||
raise RuntimeError('%s() called while another coroutine is '
|
raise RuntimeError(
|
||||||
'already waiting for incoming data' % func_name)
|
f'{func_name}() called while another coroutine is '
|
||||||
|
f'already waiting for incoming data')
|
||||||
|
|
||||||
assert not self._eof, '_wait_for_data after EOF'
|
assert not self._eof, '_wait_for_data after EOF'
|
||||||
|
|
||||||
@@ -455,12 +542,11 @@ class StreamReader:
|
|||||||
|
|
||||||
self._waiter = self._loop.create_future()
|
self._waiter = self._loop.create_future()
|
||||||
try:
|
try:
|
||||||
yield from self._waiter
|
await self._waiter
|
||||||
finally:
|
finally:
|
||||||
self._waiter = None
|
self._waiter = None
|
||||||
|
|
||||||
@coroutine
|
async def readline(self):
|
||||||
def readline(self):
|
|
||||||
"""Read chunk of data from the stream until newline (b'\n') is found.
|
"""Read chunk of data from the stream until newline (b'\n') is found.
|
||||||
|
|
||||||
On success, return chunk that ends with newline. If only partial
|
On success, return chunk that ends with newline. If only partial
|
||||||
@@ -479,10 +565,10 @@ class StreamReader:
|
|||||||
sep = b'\n'
|
sep = b'\n'
|
||||||
seplen = len(sep)
|
seplen = len(sep)
|
||||||
try:
|
try:
|
||||||
line = yield from self.readuntil(sep)
|
line = await self.readuntil(sep)
|
||||||
except IncompleteReadError as e:
|
except exceptions.IncompleteReadError as e:
|
||||||
return e.partial
|
return e.partial
|
||||||
except LimitOverrunError as e:
|
except exceptions.LimitOverrunError as e:
|
||||||
if self._buffer.startswith(sep, e.consumed):
|
if self._buffer.startswith(sep, e.consumed):
|
||||||
del self._buffer[:e.consumed + seplen]
|
del self._buffer[:e.consumed + seplen]
|
||||||
else:
|
else:
|
||||||
@@ -491,8 +577,7 @@ class StreamReader:
|
|||||||
raise ValueError(e.args[0])
|
raise ValueError(e.args[0])
|
||||||
return line
|
return line
|
||||||
|
|
||||||
@coroutine
|
async def readuntil(self, separator=b'\n'):
|
||||||
def readuntil(self, separator=b'\n'):
|
|
||||||
"""Read data from the stream until ``separator`` is found.
|
"""Read data from the stream until ``separator`` is found.
|
||||||
|
|
||||||
On success, the data and separator will be removed from the
|
On success, the data and separator will be removed from the
|
||||||
@@ -558,7 +643,7 @@ class StreamReader:
|
|||||||
# see upper comment for explanation.
|
# see upper comment for explanation.
|
||||||
offset = buflen + 1 - seplen
|
offset = buflen + 1 - seplen
|
||||||
if offset > self._limit:
|
if offset > self._limit:
|
||||||
raise LimitOverrunError(
|
raise exceptions.LimitOverrunError(
|
||||||
'Separator is not found, and chunk exceed the limit',
|
'Separator is not found, and chunk exceed the limit',
|
||||||
offset)
|
offset)
|
||||||
|
|
||||||
@@ -569,13 +654,13 @@ class StreamReader:
|
|||||||
if self._eof:
|
if self._eof:
|
||||||
chunk = bytes(self._buffer)
|
chunk = bytes(self._buffer)
|
||||||
self._buffer.clear()
|
self._buffer.clear()
|
||||||
raise IncompleteReadError(chunk, None)
|
raise exceptions.IncompleteReadError(chunk, None)
|
||||||
|
|
||||||
# _wait_for_data() will resume reading if stream was paused.
|
# _wait_for_data() will resume reading if stream was paused.
|
||||||
yield from self._wait_for_data('readuntil')
|
await self._wait_for_data('readuntil')
|
||||||
|
|
||||||
if isep > self._limit:
|
if isep > self._limit:
|
||||||
raise LimitOverrunError(
|
raise exceptions.LimitOverrunError(
|
||||||
'Separator is found, but chunk is longer than limit', isep)
|
'Separator is found, but chunk is longer than limit', isep)
|
||||||
|
|
||||||
chunk = self._buffer[:isep + seplen]
|
chunk = self._buffer[:isep + seplen]
|
||||||
@@ -583,20 +668,20 @@ class StreamReader:
|
|||||||
self._maybe_resume_transport()
|
self._maybe_resume_transport()
|
||||||
return bytes(chunk)
|
return bytes(chunk)
|
||||||
|
|
||||||
@coroutine
|
async def read(self, n=-1):
|
||||||
def read(self, n=-1):
|
|
||||||
"""Read up to `n` bytes from the stream.
|
"""Read up to `n` bytes from the stream.
|
||||||
|
|
||||||
If n is not provided, or set to -1, read until EOF and return all read
|
If `n` is not provided or set to -1,
|
||||||
bytes. If the EOF was received and the internal buffer is empty, return
|
read until EOF, then return all read bytes.
|
||||||
an empty bytes object.
|
If EOF was received and the internal buffer is empty,
|
||||||
|
return an empty bytes object.
|
||||||
|
|
||||||
If n is zero, return empty bytes object immediately.
|
If `n` is 0, return an empty bytes object immediately.
|
||||||
|
|
||||||
If n is positive, this function try to read `n` bytes, and may return
|
If `n` is positive, return at most `n` available bytes
|
||||||
less or equal bytes than requested, but at least one byte. If EOF was
|
as soon as at least 1 byte is available in the internal buffer.
|
||||||
received before any byte is read, this function returns empty byte
|
If EOF is received before any byte is read, return an empty
|
||||||
object.
|
bytes object.
|
||||||
|
|
||||||
Returned value is not limited with limit, configured at stream
|
Returned value is not limited with limit, configured at stream
|
||||||
creation.
|
creation.
|
||||||
@@ -618,24 +703,23 @@ class StreamReader:
|
|||||||
# bytes. So just call self.read(self._limit) until EOF.
|
# bytes. So just call self.read(self._limit) until EOF.
|
||||||
blocks = []
|
blocks = []
|
||||||
while True:
|
while True:
|
||||||
block = yield from self.read(self._limit)
|
block = await self.read(self._limit)
|
||||||
if not block:
|
if not block:
|
||||||
break
|
break
|
||||||
blocks.append(block)
|
blocks.append(block)
|
||||||
return b''.join(blocks)
|
return b''.join(blocks)
|
||||||
|
|
||||||
if not self._buffer and not self._eof:
|
if not self._buffer and not self._eof:
|
||||||
yield from self._wait_for_data('read')
|
await self._wait_for_data('read')
|
||||||
|
|
||||||
# This will work right even if buffer is less than n bytes
|
# This will work right even if buffer is less than n bytes
|
||||||
data = bytes(self._buffer[:n])
|
data = bytes(memoryview(self._buffer)[:n])
|
||||||
del self._buffer[:n]
|
del self._buffer[:n]
|
||||||
|
|
||||||
self._maybe_resume_transport()
|
self._maybe_resume_transport()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@coroutine
|
async def readexactly(self, n):
|
||||||
def readexactly(self, n):
|
|
||||||
"""Read exactly `n` bytes.
|
"""Read exactly `n` bytes.
|
||||||
|
|
||||||
Raise an IncompleteReadError if EOF is reached before `n` bytes can be
|
Raise an IncompleteReadError if EOF is reached before `n` bytes can be
|
||||||
@@ -663,33 +747,24 @@ class StreamReader:
|
|||||||
if self._eof:
|
if self._eof:
|
||||||
incomplete = bytes(self._buffer)
|
incomplete = bytes(self._buffer)
|
||||||
self._buffer.clear()
|
self._buffer.clear()
|
||||||
raise IncompleteReadError(incomplete, n)
|
raise exceptions.IncompleteReadError(incomplete, n)
|
||||||
|
|
||||||
yield from self._wait_for_data('readexactly')
|
await self._wait_for_data('readexactly')
|
||||||
|
|
||||||
if len(self._buffer) == n:
|
if len(self._buffer) == n:
|
||||||
data = bytes(self._buffer)
|
data = bytes(self._buffer)
|
||||||
self._buffer.clear()
|
self._buffer.clear()
|
||||||
else:
|
else:
|
||||||
data = bytes(self._buffer[:n])
|
data = bytes(memoryview(self._buffer)[:n])
|
||||||
del self._buffer[:n]
|
del self._buffer[:n]
|
||||||
self._maybe_resume_transport()
|
self._maybe_resume_transport()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
if compat.PY35:
|
def __aiter__(self):
|
||||||
@coroutine
|
return self
|
||||||
def __aiter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
@coroutine
|
async def __anext__(self):
|
||||||
def __anext__(self):
|
val = await self.readline()
|
||||||
val = yield from self.readline()
|
if val == b'':
|
||||||
if val == b'':
|
raise StopAsyncIteration
|
||||||
raise StopAsyncIteration
|
return val
|
||||||
return val
|
|
||||||
|
|
||||||
if compat.PY352:
|
|
||||||
# In Python 3.5.2 and greater, __aiter__ should return
|
|
||||||
# the asynchronous iterator directly.
|
|
||||||
def __aiter__(self):
|
|
||||||
return self
|
|
||||||
|
|||||||
126
Lib/asyncio/subprocess.py
vendored
126
Lib/asyncio/subprocess.py
vendored
@@ -1,4 +1,4 @@
|
|||||||
__all__ = ['create_subprocess_exec', 'create_subprocess_shell']
|
__all__ = 'create_subprocess_exec', 'create_subprocess_shell'
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -6,7 +6,6 @@ from . import events
|
|||||||
from . import protocols
|
from . import protocols
|
||||||
from . import streams
|
from . import streams
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -24,16 +23,19 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
|||||||
self._limit = limit
|
self._limit = limit
|
||||||
self.stdin = self.stdout = self.stderr = None
|
self.stdin = self.stdout = self.stderr = None
|
||||||
self._transport = None
|
self._transport = None
|
||||||
|
self._process_exited = False
|
||||||
|
self._pipe_fds = []
|
||||||
|
self._stdin_closed = self._loop.create_future()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
info = [self.__class__.__name__]
|
info = [self.__class__.__name__]
|
||||||
if self.stdin is not None:
|
if self.stdin is not None:
|
||||||
info.append('stdin=%r' % self.stdin)
|
info.append(f'stdin={self.stdin!r}')
|
||||||
if self.stdout is not None:
|
if self.stdout is not None:
|
||||||
info.append('stdout=%r' % self.stdout)
|
info.append(f'stdout={self.stdout!r}')
|
||||||
if self.stderr is not None:
|
if self.stderr is not None:
|
||||||
info.append('stderr=%r' % self.stderr)
|
info.append(f'stderr={self.stderr!r}')
|
||||||
return '<%s>' % ' '.join(info)
|
return '<{}>'.format(' '.join(info))
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport):
|
||||||
self._transport = transport
|
self._transport = transport
|
||||||
@@ -43,12 +45,14 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
|||||||
self.stdout = streams.StreamReader(limit=self._limit,
|
self.stdout = streams.StreamReader(limit=self._limit,
|
||||||
loop=self._loop)
|
loop=self._loop)
|
||||||
self.stdout.set_transport(stdout_transport)
|
self.stdout.set_transport(stdout_transport)
|
||||||
|
self._pipe_fds.append(1)
|
||||||
|
|
||||||
stderr_transport = transport.get_pipe_transport(2)
|
stderr_transport = transport.get_pipe_transport(2)
|
||||||
if stderr_transport is not None:
|
if stderr_transport is not None:
|
||||||
self.stderr = streams.StreamReader(limit=self._limit,
|
self.stderr = streams.StreamReader(limit=self._limit,
|
||||||
loop=self._loop)
|
loop=self._loop)
|
||||||
self.stderr.set_transport(stderr_transport)
|
self.stderr.set_transport(stderr_transport)
|
||||||
|
self._pipe_fds.append(2)
|
||||||
|
|
||||||
stdin_transport = transport.get_pipe_transport(0)
|
stdin_transport = transport.get_pipe_transport(0)
|
||||||
if stdin_transport is not None:
|
if stdin_transport is not None:
|
||||||
@@ -73,6 +77,13 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
|||||||
if pipe is not None:
|
if pipe is not None:
|
||||||
pipe.close()
|
pipe.close()
|
||||||
self.connection_lost(exc)
|
self.connection_lost(exc)
|
||||||
|
if exc is None:
|
||||||
|
self._stdin_closed.set_result(None)
|
||||||
|
else:
|
||||||
|
self._stdin_closed.set_exception(exc)
|
||||||
|
# Since calling `wait_closed()` is not mandatory,
|
||||||
|
# we shouldn't log the traceback if this is not awaited.
|
||||||
|
self._stdin_closed._log_traceback = False
|
||||||
return
|
return
|
||||||
if fd == 1:
|
if fd == 1:
|
||||||
reader = self.stdout
|
reader = self.stdout
|
||||||
@@ -80,15 +91,28 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
|||||||
reader = self.stderr
|
reader = self.stderr
|
||||||
else:
|
else:
|
||||||
reader = None
|
reader = None
|
||||||
if reader != None:
|
if reader is not None:
|
||||||
if exc is None:
|
if exc is None:
|
||||||
reader.feed_eof()
|
reader.feed_eof()
|
||||||
else:
|
else:
|
||||||
reader.set_exception(exc)
|
reader.set_exception(exc)
|
||||||
|
|
||||||
|
if fd in self._pipe_fds:
|
||||||
|
self._pipe_fds.remove(fd)
|
||||||
|
self._maybe_close_transport()
|
||||||
|
|
||||||
def process_exited(self):
|
def process_exited(self):
|
||||||
self._transport.close()
|
self._process_exited = True
|
||||||
self._transport = None
|
self._maybe_close_transport()
|
||||||
|
|
||||||
|
def _maybe_close_transport(self):
|
||||||
|
if len(self._pipe_fds) == 0 and self._process_exited:
|
||||||
|
self._transport.close()
|
||||||
|
self._transport = None
|
||||||
|
|
||||||
|
def _get_close_waiter(self, stream):
|
||||||
|
if stream is self.stdin:
|
||||||
|
return self._stdin_closed
|
||||||
|
|
||||||
|
|
||||||
class Process:
|
class Process:
|
||||||
@@ -102,18 +126,15 @@ class Process:
|
|||||||
self.pid = transport.get_pid()
|
self.pid = transport.get_pid()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s %s>' % (self.__class__.__name__, self.pid)
|
return f'<{self.__class__.__name__} {self.pid}>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def returncode(self):
|
def returncode(self):
|
||||||
return self._transport.get_returncode()
|
return self._transport.get_returncode()
|
||||||
|
|
||||||
@coroutine
|
async def wait(self):
|
||||||
def wait(self):
|
"""Wait until the process exit and return the process return code."""
|
||||||
"""Wait until the process exit and return the process return code.
|
return await self._transport._wait()
|
||||||
|
|
||||||
This method is a coroutine."""
|
|
||||||
return (yield from self._transport._wait())
|
|
||||||
|
|
||||||
def send_signal(self, signal):
|
def send_signal(self, signal):
|
||||||
self._transport.send_signal(signal)
|
self._transport.send_signal(signal)
|
||||||
@@ -124,17 +145,19 @@ class Process:
|
|||||||
def kill(self):
|
def kill(self):
|
||||||
self._transport.kill()
|
self._transport.kill()
|
||||||
|
|
||||||
@coroutine
|
async def _feed_stdin(self, input):
|
||||||
def _feed_stdin(self, input):
|
|
||||||
debug = self._loop.get_debug()
|
debug = self._loop.get_debug()
|
||||||
self.stdin.write(input)
|
|
||||||
if debug:
|
|
||||||
logger.debug('%r communicate: feed stdin (%s bytes)',
|
|
||||||
self, len(input))
|
|
||||||
try:
|
try:
|
||||||
yield from self.stdin.drain()
|
if input is not None:
|
||||||
|
self.stdin.write(input)
|
||||||
|
if debug:
|
||||||
|
logger.debug(
|
||||||
|
'%r communicate: feed stdin (%s bytes)', self, len(input))
|
||||||
|
|
||||||
|
await self.stdin.drain()
|
||||||
except (BrokenPipeError, ConnectionResetError) as exc:
|
except (BrokenPipeError, ConnectionResetError) as exc:
|
||||||
# communicate() ignores BrokenPipeError and ConnectionResetError
|
# communicate() ignores BrokenPipeError and ConnectionResetError.
|
||||||
|
# write() and drain() can raise these exceptions.
|
||||||
if debug:
|
if debug:
|
||||||
logger.debug('%r communicate: stdin got %r', self, exc)
|
logger.debug('%r communicate: stdin got %r', self, exc)
|
||||||
|
|
||||||
@@ -142,12 +165,10 @@ class Process:
|
|||||||
logger.debug('%r communicate: close stdin', self)
|
logger.debug('%r communicate: close stdin', self)
|
||||||
self.stdin.close()
|
self.stdin.close()
|
||||||
|
|
||||||
@coroutine
|
async def _noop(self):
|
||||||
def _noop(self):
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@coroutine
|
async def _read_stream(self, fd):
|
||||||
def _read_stream(self, fd):
|
|
||||||
transport = self._transport.get_pipe_transport(fd)
|
transport = self._transport.get_pipe_transport(fd)
|
||||||
if fd == 2:
|
if fd == 2:
|
||||||
stream = self.stderr
|
stream = self.stderr
|
||||||
@@ -157,16 +178,15 @@ class Process:
|
|||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
name = 'stdout' if fd == 1 else 'stderr'
|
name = 'stdout' if fd == 1 else 'stderr'
|
||||||
logger.debug('%r communicate: read %s', self, name)
|
logger.debug('%r communicate: read %s', self, name)
|
||||||
output = yield from stream.read()
|
output = await stream.read()
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
name = 'stdout' if fd == 1 else 'stderr'
|
name = 'stdout' if fd == 1 else 'stderr'
|
||||||
logger.debug('%r communicate: close %s', self, name)
|
logger.debug('%r communicate: close %s', self, name)
|
||||||
transport.close()
|
transport.close()
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@coroutine
|
async def communicate(self, input=None):
|
||||||
def communicate(self, input=None):
|
if self.stdin is not None:
|
||||||
if input is not None:
|
|
||||||
stdin = self._feed_stdin(input)
|
stdin = self._feed_stdin(input)
|
||||||
else:
|
else:
|
||||||
stdin = self._noop()
|
stdin = self._noop()
|
||||||
@@ -178,36 +198,32 @@ class Process:
|
|||||||
stderr = self._read_stream(2)
|
stderr = self._read_stream(2)
|
||||||
else:
|
else:
|
||||||
stderr = self._noop()
|
stderr = self._noop()
|
||||||
stdin, stdout, stderr = yield from tasks.gather(stdin, stdout, stderr,
|
stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr)
|
||||||
loop=self._loop)
|
await self.wait()
|
||||||
yield from self.wait()
|
|
||||||
return (stdout, stderr)
|
return (stdout, stderr)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
|
||||||
def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
|
limit=streams._DEFAULT_LIMIT, **kwds):
|
||||||
loop=None, limit=streams._DEFAULT_LIMIT, **kwds):
|
loop = events.get_running_loop()
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
||||||
loop=loop)
|
loop=loop)
|
||||||
transport, protocol = yield from loop.subprocess_shell(
|
transport, protocol = await loop.subprocess_shell(
|
||||||
protocol_factory,
|
protocol_factory,
|
||||||
cmd, stdin=stdin, stdout=stdout,
|
cmd, stdin=stdin, stdout=stdout,
|
||||||
stderr=stderr, **kwds)
|
stderr=stderr, **kwds)
|
||||||
return Process(transport, protocol, loop)
|
return Process(transport, protocol, loop)
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def create_subprocess_exec(program, *args, stdin=None, stdout=None,
|
async def create_subprocess_exec(program, *args, stdin=None, stdout=None,
|
||||||
stderr=None, loop=None,
|
stderr=None, limit=streams._DEFAULT_LIMIT,
|
||||||
limit=streams._DEFAULT_LIMIT, **kwds):
|
**kwds):
|
||||||
if loop is None:
|
loop = events.get_running_loop()
|
||||||
loop = events.get_event_loop()
|
|
||||||
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
||||||
loop=loop)
|
loop=loop)
|
||||||
transport, protocol = yield from loop.subprocess_exec(
|
transport, protocol = await loop.subprocess_exec(
|
||||||
protocol_factory,
|
protocol_factory,
|
||||||
program, *args,
|
program, *args,
|
||||||
stdin=stdin, stdout=stdout,
|
stdin=stdin, stdout=stdout,
|
||||||
stderr=stderr, **kwds)
|
stderr=stderr, **kwds)
|
||||||
return Process(transport, protocol, loop)
|
return Process(transport, protocol, loop)
|
||||||
|
|||||||
240
Lib/asyncio/taskgroups.py
vendored
Normal file
240
Lib/asyncio/taskgroups.py
vendored
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# Adapted with permission from the EdgeDB project;
|
||||||
|
# license: PSFL.
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ("TaskGroup",)
|
||||||
|
|
||||||
|
from . import events
|
||||||
|
from . import exceptions
|
||||||
|
from . import tasks
|
||||||
|
|
||||||
|
|
||||||
|
class TaskGroup:
|
||||||
|
"""Asynchronous context manager for managing groups of tasks.
|
||||||
|
|
||||||
|
Example use:
|
||||||
|
|
||||||
|
async with asyncio.TaskGroup() as group:
|
||||||
|
task1 = group.create_task(some_coroutine(...))
|
||||||
|
task2 = group.create_task(other_coroutine(...))
|
||||||
|
print("Both tasks have completed now.")
|
||||||
|
|
||||||
|
All tasks are awaited when the context manager exits.
|
||||||
|
|
||||||
|
Any exceptions other than `asyncio.CancelledError` raised within
|
||||||
|
a task will cancel all remaining tasks and wait for them to exit.
|
||||||
|
The exceptions are then combined and raised as an `ExceptionGroup`.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self._entered = False
|
||||||
|
self._exiting = False
|
||||||
|
self._aborting = False
|
||||||
|
self._loop = None
|
||||||
|
self._parent_task = None
|
||||||
|
self._parent_cancel_requested = False
|
||||||
|
self._tasks = set()
|
||||||
|
self._errors = []
|
||||||
|
self._base_error = None
|
||||||
|
self._on_completed_fut = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
info = ['']
|
||||||
|
if self._tasks:
|
||||||
|
info.append(f'tasks={len(self._tasks)}')
|
||||||
|
if self._errors:
|
||||||
|
info.append(f'errors={len(self._errors)}')
|
||||||
|
if self._aborting:
|
||||||
|
info.append('cancelling')
|
||||||
|
elif self._entered:
|
||||||
|
info.append('entered')
|
||||||
|
|
||||||
|
info_str = ' '.join(info)
|
||||||
|
return f'<TaskGroup{info_str}>'
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
if self._entered:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"TaskGroup {self!r} has already been entered")
|
||||||
|
if self._loop is None:
|
||||||
|
self._loop = events.get_running_loop()
|
||||||
|
self._parent_task = tasks.current_task(self._loop)
|
||||||
|
if self._parent_task is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'TaskGroup {self!r} cannot determine the parent task')
|
||||||
|
self._entered = True
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, et, exc, tb):
|
||||||
|
self._exiting = True
|
||||||
|
|
||||||
|
if (exc is not None and
|
||||||
|
self._is_base_error(exc) and
|
||||||
|
self._base_error is None):
|
||||||
|
self._base_error = exc
|
||||||
|
|
||||||
|
propagate_cancellation_error = \
|
||||||
|
exc if et is exceptions.CancelledError else None
|
||||||
|
if self._parent_cancel_requested:
|
||||||
|
# If this flag is set we *must* call uncancel().
|
||||||
|
if self._parent_task.uncancel() == 0:
|
||||||
|
# If there are no pending cancellations left,
|
||||||
|
# don't propagate CancelledError.
|
||||||
|
propagate_cancellation_error = None
|
||||||
|
|
||||||
|
if et is not None:
|
||||||
|
if not self._aborting:
|
||||||
|
# Our parent task is being cancelled:
|
||||||
|
#
|
||||||
|
# async with TaskGroup() as g:
|
||||||
|
# g.create_task(...)
|
||||||
|
# await ... # <- CancelledError
|
||||||
|
#
|
||||||
|
# or there's an exception in "async with":
|
||||||
|
#
|
||||||
|
# async with TaskGroup() as g:
|
||||||
|
# g.create_task(...)
|
||||||
|
# 1 / 0
|
||||||
|
#
|
||||||
|
self._abort()
|
||||||
|
|
||||||
|
# We use while-loop here because "self._on_completed_fut"
|
||||||
|
# can be cancelled multiple times if our parent task
|
||||||
|
# is being cancelled repeatedly (or even once, when
|
||||||
|
# our own cancellation is already in progress)
|
||||||
|
while self._tasks:
|
||||||
|
if self._on_completed_fut is None:
|
||||||
|
self._on_completed_fut = self._loop.create_future()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self._on_completed_fut
|
||||||
|
except exceptions.CancelledError as ex:
|
||||||
|
if not self._aborting:
|
||||||
|
# Our parent task is being cancelled:
|
||||||
|
#
|
||||||
|
# async def wrapper():
|
||||||
|
# async with TaskGroup() as g:
|
||||||
|
# g.create_task(foo)
|
||||||
|
#
|
||||||
|
# "wrapper" is being cancelled while "foo" is
|
||||||
|
# still running.
|
||||||
|
propagate_cancellation_error = ex
|
||||||
|
self._abort()
|
||||||
|
|
||||||
|
self._on_completed_fut = None
|
||||||
|
|
||||||
|
assert not self._tasks
|
||||||
|
|
||||||
|
if self._base_error is not None:
|
||||||
|
raise self._base_error
|
||||||
|
|
||||||
|
# Propagate CancelledError if there is one, except if there
|
||||||
|
# are other errors -- those have priority.
|
||||||
|
if propagate_cancellation_error and not self._errors:
|
||||||
|
raise propagate_cancellation_error
|
||||||
|
|
||||||
|
if et is not None and et is not exceptions.CancelledError:
|
||||||
|
self._errors.append(exc)
|
||||||
|
|
||||||
|
if self._errors:
|
||||||
|
# Exceptions are heavy objects that can have object
|
||||||
|
# cycles (bad for GC); let's not keep a reference to
|
||||||
|
# a bunch of them.
|
||||||
|
try:
|
||||||
|
me = BaseExceptionGroup('unhandled errors in a TaskGroup', self._errors)
|
||||||
|
raise me from None
|
||||||
|
finally:
|
||||||
|
self._errors = None
|
||||||
|
|
||||||
|
def create_task(self, coro, *, name=None, context=None):
|
||||||
|
"""Create a new task in this group and return it.
|
||||||
|
|
||||||
|
Similar to `asyncio.create_task`.
|
||||||
|
"""
|
||||||
|
if not self._entered:
|
||||||
|
raise RuntimeError(f"TaskGroup {self!r} has not been entered")
|
||||||
|
if self._exiting and not self._tasks:
|
||||||
|
raise RuntimeError(f"TaskGroup {self!r} is finished")
|
||||||
|
if self._aborting:
|
||||||
|
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
|
||||||
|
if context is None:
|
||||||
|
task = self._loop.create_task(coro)
|
||||||
|
else:
|
||||||
|
task = self._loop.create_task(coro, context=context)
|
||||||
|
tasks._set_task_name(task, name)
|
||||||
|
# optimization: Immediately call the done callback if the task is
|
||||||
|
# already done (e.g. if the coro was able to complete eagerly),
|
||||||
|
# and skip scheduling a done callback
|
||||||
|
if task.done():
|
||||||
|
self._on_task_done(task)
|
||||||
|
else:
|
||||||
|
self._tasks.add(task)
|
||||||
|
task.add_done_callback(self._on_task_done)
|
||||||
|
return task
|
||||||
|
|
||||||
|
# Since Python 3.8 Tasks propagate all exceptions correctly,
|
||||||
|
# except for KeyboardInterrupt and SystemExit which are
|
||||||
|
# still considered special.
|
||||||
|
|
||||||
|
def _is_base_error(self, exc: BaseException) -> bool:
|
||||||
|
assert isinstance(exc, BaseException)
|
||||||
|
return isinstance(exc, (SystemExit, KeyboardInterrupt))
|
||||||
|
|
||||||
|
def _abort(self):
|
||||||
|
self._aborting = True
|
||||||
|
|
||||||
|
for t in self._tasks:
|
||||||
|
if not t.done():
|
||||||
|
t.cancel()
|
||||||
|
|
||||||
|
def _on_task_done(self, task):
|
||||||
|
self._tasks.discard(task)
|
||||||
|
|
||||||
|
if self._on_completed_fut is not None and not self._tasks:
|
||||||
|
if not self._on_completed_fut.done():
|
||||||
|
self._on_completed_fut.set_result(True)
|
||||||
|
|
||||||
|
if task.cancelled():
|
||||||
|
return
|
||||||
|
|
||||||
|
exc = task.exception()
|
||||||
|
if exc is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._errors.append(exc)
|
||||||
|
if self._is_base_error(exc) and self._base_error is None:
|
||||||
|
self._base_error = exc
|
||||||
|
|
||||||
|
if self._parent_task.done():
|
||||||
|
# Not sure if this case is possible, but we want to handle
|
||||||
|
# it anyways.
|
||||||
|
self._loop.call_exception_handler({
|
||||||
|
'message': f'Task {task!r} has errored out but its parent '
|
||||||
|
f'task {self._parent_task} is already completed',
|
||||||
|
'exception': exc,
|
||||||
|
'task': task,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._aborting and not self._parent_cancel_requested:
|
||||||
|
# If parent task *is not* being cancelled, it means that we want
|
||||||
|
# to manually cancel it to abort whatever is being run right now
|
||||||
|
# in the TaskGroup. But we want to mark parent task as
|
||||||
|
# "not cancelled" later in __aexit__. Example situation that
|
||||||
|
# we need to handle:
|
||||||
|
#
|
||||||
|
# async def foo():
|
||||||
|
# try:
|
||||||
|
# async with TaskGroup() as g:
|
||||||
|
# g.create_task(crash_soon())
|
||||||
|
# await something # <- this needs to be canceled
|
||||||
|
# # by the TaskGroup, e.g.
|
||||||
|
# # foo() needs to be cancelled
|
||||||
|
# except Exception:
|
||||||
|
# # Ignore any exceptions raised in the TaskGroup
|
||||||
|
# pass
|
||||||
|
# await something_else # this line has to be called
|
||||||
|
# # after TaskGroup is finished.
|
||||||
|
self._abort()
|
||||||
|
self._parent_cancel_requested = True
|
||||||
|
self._parent_task.cancel()
|
||||||
853
Lib/asyncio/tasks.py
vendored
853
Lib/asyncio/tasks.py
vendored
File diff suppressed because it is too large
Load Diff
503
Lib/asyncio/test_utils.py
vendored
503
Lib/asyncio/test_utils.py
vendored
@@ -1,503 +0,0 @@
|
|||||||
"""Utilities shared by tests."""
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import contextlib
|
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import socket
|
|
||||||
import socketserver
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from http.server import HTTPServer
|
|
||||||
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
|
|
||||||
|
|
||||||
try:
|
|
||||||
import ssl
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
ssl = None
|
|
||||||
|
|
||||||
from . import base_events
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import futures
|
|
||||||
from . import selectors
|
|
||||||
from . import tasks
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == 'win32': # pragma: no cover
|
|
||||||
from .windows_utils import socketpair
|
|
||||||
else:
|
|
||||||
from socket import socketpair # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
def dummy_ssl_context():
|
|
||||||
if ssl is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
||||||
|
|
||||||
|
|
||||||
def run_briefly(loop):
|
|
||||||
@coroutine
|
|
||||||
def once():
|
|
||||||
pass
|
|
||||||
gen = once()
|
|
||||||
t = loop.create_task(gen)
|
|
||||||
# Don't log a warning if the task is not done after run_until_complete().
|
|
||||||
# It occurs if the loop is stopped or if a task raises a BaseException.
|
|
||||||
t._log_destroy_pending = False
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(t)
|
|
||||||
finally:
|
|
||||||
gen.close()
|
|
||||||
|
|
||||||
|
|
||||||
def run_until(loop, pred, timeout=30):
|
|
||||||
deadline = time.time() + timeout
|
|
||||||
while not pred():
|
|
||||||
if timeout is not None:
|
|
||||||
timeout = deadline - time.time()
|
|
||||||
if timeout <= 0:
|
|
||||||
raise futures.TimeoutError()
|
|
||||||
loop.run_until_complete(tasks.sleep(0.001, loop=loop))
|
|
||||||
|
|
||||||
|
|
||||||
def run_once(loop):
|
|
||||||
"""Legacy API to run once through the event loop.
|
|
||||||
|
|
||||||
This is the recommended pattern for test code. It will poll the
|
|
||||||
selector once and run all callbacks scheduled in response to I/O
|
|
||||||
events.
|
|
||||||
"""
|
|
||||||
loop.call_soon(loop.stop)
|
|
||||||
loop.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
class SilentWSGIRequestHandler(WSGIRequestHandler):
|
|
||||||
|
|
||||||
def get_stderr(self):
|
|
||||||
return io.StringIO()
|
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SilentWSGIServer(WSGIServer):
|
|
||||||
|
|
||||||
request_timeout = 2
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
request, client_addr = super().get_request()
|
|
||||||
request.settimeout(self.request_timeout)
|
|
||||||
return request, client_addr
|
|
||||||
|
|
||||||
def handle_error(self, request, client_address):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SSLWSGIServerMixin:
|
|
||||||
|
|
||||||
def finish_request(self, request, client_address):
|
|
||||||
# The relative location of our test directory (which
|
|
||||||
# contains the ssl key and certificate files) differs
|
|
||||||
# between the stdlib and stand-alone asyncio.
|
|
||||||
# Prefer our own if we can find it.
|
|
||||||
here = os.path.join(os.path.dirname(__file__), '..', 'tests')
|
|
||||||
if not os.path.isdir(here):
|
|
||||||
here = os.path.join(os.path.dirname(os.__file__),
|
|
||||||
'test', 'test_asyncio')
|
|
||||||
keyfile = os.path.join(here, 'ssl_key.pem')
|
|
||||||
certfile = os.path.join(here, 'ssl_cert.pem')
|
|
||||||
context = ssl.SSLContext()
|
|
||||||
context.load_cert_chain(certfile, keyfile)
|
|
||||||
|
|
||||||
ssock = context.wrap_socket(request, server_side=True)
|
|
||||||
try:
|
|
||||||
self.RequestHandlerClass(ssock, client_address, self)
|
|
||||||
ssock.close()
|
|
||||||
except OSError:
|
|
||||||
# maybe socket has been closed by peer
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SSLWSGIServer(SSLWSGIServerMixin, SilentWSGIServer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _run_test_server(*, address, use_ssl=False, server_cls, server_ssl_cls):
|
|
||||||
|
|
||||||
def app(environ, start_response):
|
|
||||||
status = '200 OK'
|
|
||||||
headers = [('Content-type', 'text/plain')]
|
|
||||||
start_response(status, headers)
|
|
||||||
return [b'Test message']
|
|
||||||
|
|
||||||
# Run the test WSGI server in a separate thread in order not to
|
|
||||||
# interfere with event handling in the main thread
|
|
||||||
server_class = server_ssl_cls if use_ssl else server_cls
|
|
||||||
httpd = server_class(address, SilentWSGIRequestHandler)
|
|
||||||
httpd.set_app(app)
|
|
||||||
httpd.address = httpd.server_address
|
|
||||||
server_thread = threading.Thread(
|
|
||||||
target=lambda: httpd.serve_forever(poll_interval=0.05))
|
|
||||||
server_thread.start()
|
|
||||||
try:
|
|
||||||
yield httpd
|
|
||||||
finally:
|
|
||||||
httpd.shutdown()
|
|
||||||
httpd.server_close()
|
|
||||||
server_thread.join()
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
|
||||||
|
|
||||||
class UnixHTTPServer(socketserver.UnixStreamServer, HTTPServer):
|
|
||||||
|
|
||||||
def server_bind(self):
|
|
||||||
socketserver.UnixStreamServer.server_bind(self)
|
|
||||||
self.server_name = '127.0.0.1'
|
|
||||||
self.server_port = 80
|
|
||||||
|
|
||||||
|
|
||||||
class UnixWSGIServer(UnixHTTPServer, WSGIServer):
|
|
||||||
|
|
||||||
request_timeout = 2
|
|
||||||
|
|
||||||
def server_bind(self):
|
|
||||||
UnixHTTPServer.server_bind(self)
|
|
||||||
self.setup_environ()
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
request, client_addr = super().get_request()
|
|
||||||
request.settimeout(self.request_timeout)
|
|
||||||
# Code in the stdlib expects that get_request
|
|
||||||
# will return a socket and a tuple (host, port).
|
|
||||||
# However, this isn't true for UNIX sockets,
|
|
||||||
# as the second return value will be a path;
|
|
||||||
# hence we return some fake data sufficient
|
|
||||||
# to get the tests going
|
|
||||||
return request, ('127.0.0.1', '')
|
|
||||||
|
|
||||||
|
|
||||||
class SilentUnixWSGIServer(UnixWSGIServer):
|
|
||||||
|
|
||||||
def handle_error(self, request, client_address):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnixSSLWSGIServer(SSLWSGIServerMixin, SilentUnixWSGIServer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def gen_unix_socket_path():
|
|
||||||
with tempfile.NamedTemporaryFile() as file:
|
|
||||||
return file.name
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def unix_socket_path():
|
|
||||||
path = gen_unix_socket_path()
|
|
||||||
try:
|
|
||||||
yield path
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
os.unlink(path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def run_test_unix_server(*, use_ssl=False):
|
|
||||||
with unix_socket_path() as path:
|
|
||||||
yield from _run_test_server(address=path, use_ssl=use_ssl,
|
|
||||||
server_cls=SilentUnixWSGIServer,
|
|
||||||
server_ssl_cls=UnixSSLWSGIServer)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def run_test_server(*, host='127.0.0.1', port=0, use_ssl=False):
|
|
||||||
yield from _run_test_server(address=(host, port), use_ssl=use_ssl,
|
|
||||||
server_cls=SilentWSGIServer,
|
|
||||||
server_ssl_cls=SSLWSGIServer)
|
|
||||||
|
|
||||||
|
|
||||||
def make_test_protocol(base):
|
|
||||||
dct = {}
|
|
||||||
for name in dir(base):
|
|
||||||
if name.startswith('__') and name.endswith('__'):
|
|
||||||
# skip magic names
|
|
||||||
continue
|
|
||||||
dct[name] = MockCallback(return_value=None)
|
|
||||||
return type('TestProtocol', (base,) + base.__bases__, dct)()
|
|
||||||
|
|
||||||
|
|
||||||
class TestSelector(selectors.BaseSelector):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.keys = {}
|
|
||||||
|
|
||||||
def register(self, fileobj, events, data=None):
|
|
||||||
key = selectors.SelectorKey(fileobj, 0, events, data)
|
|
||||||
self.keys[fileobj] = key
|
|
||||||
return key
|
|
||||||
|
|
||||||
def unregister(self, fileobj):
|
|
||||||
return self.keys.pop(fileobj)
|
|
||||||
|
|
||||||
def select(self, timeout):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_map(self):
|
|
||||||
return self.keys
|
|
||||||
|
|
||||||
|
|
||||||
class TestLoop(base_events.BaseEventLoop):
|
|
||||||
"""Loop for unittests.
|
|
||||||
|
|
||||||
It manages self time directly.
|
|
||||||
If something scheduled to be executed later then
|
|
||||||
on next loop iteration after all ready handlers done
|
|
||||||
generator passed to __init__ is calling.
|
|
||||||
|
|
||||||
Generator should be like this:
|
|
||||||
|
|
||||||
def gen():
|
|
||||||
...
|
|
||||||
when = yield ...
|
|
||||||
... = yield time_advance
|
|
||||||
|
|
||||||
Value returned by yield is absolute time of next scheduled handler.
|
|
||||||
Value passed to yield is time advance to move loop's time forward.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, gen=None):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
if gen is None:
|
|
||||||
def gen():
|
|
||||||
yield
|
|
||||||
self._check_on_close = False
|
|
||||||
else:
|
|
||||||
self._check_on_close = True
|
|
||||||
|
|
||||||
self._gen = gen()
|
|
||||||
next(self._gen)
|
|
||||||
self._time = 0
|
|
||||||
self._clock_resolution = 1e-9
|
|
||||||
self._timers = []
|
|
||||||
self._selector = TestSelector()
|
|
||||||
|
|
||||||
self.readers = {}
|
|
||||||
self.writers = {}
|
|
||||||
self.reset_counters()
|
|
||||||
|
|
||||||
self._transports = weakref.WeakValueDictionary()
|
|
||||||
|
|
||||||
def time(self):
|
|
||||||
return self._time
|
|
||||||
|
|
||||||
def advance_time(self, advance):
|
|
||||||
"""Move test time forward."""
|
|
||||||
if advance:
|
|
||||||
self._time += advance
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
super().close()
|
|
||||||
if self._check_on_close:
|
|
||||||
try:
|
|
||||||
self._gen.send(0)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
else: # pragma: no cover
|
|
||||||
raise AssertionError("Time generator is not finished")
|
|
||||||
|
|
||||||
def _add_reader(self, fd, callback, *args):
|
|
||||||
self.readers[fd] = events.Handle(callback, args, self)
|
|
||||||
|
|
||||||
def _remove_reader(self, fd):
|
|
||||||
self.remove_reader_count[fd] += 1
|
|
||||||
if fd in self.readers:
|
|
||||||
del self.readers[fd]
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def assert_reader(self, fd, callback, *args):
|
|
||||||
assert fd in self.readers, 'fd {} is not registered'.format(fd)
|
|
||||||
handle = self.readers[fd]
|
|
||||||
assert handle._callback == callback, '{!r} != {!r}'.format(
|
|
||||||
handle._callback, callback)
|
|
||||||
assert handle._args == args, '{!r} != {!r}'.format(
|
|
||||||
handle._args, args)
|
|
||||||
|
|
||||||
def _add_writer(self, fd, callback, *args):
|
|
||||||
self.writers[fd] = events.Handle(callback, args, self)
|
|
||||||
|
|
||||||
def _remove_writer(self, fd):
|
|
||||||
self.remove_writer_count[fd] += 1
|
|
||||||
if fd in self.writers:
|
|
||||||
del self.writers[fd]
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def assert_writer(self, fd, callback, *args):
|
|
||||||
assert fd in self.writers, 'fd {} is not registered'.format(fd)
|
|
||||||
handle = self.writers[fd]
|
|
||||||
assert handle._callback == callback, '{!r} != {!r}'.format(
|
|
||||||
handle._callback, callback)
|
|
||||||
assert handle._args == args, '{!r} != {!r}'.format(
|
|
||||||
handle._args, args)
|
|
||||||
|
|
||||||
def _ensure_fd_no_transport(self, fd):
|
|
||||||
try:
|
|
||||||
transport = self._transports[fd]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise RuntimeError(
|
|
||||||
'File descriptor {!r} is used by transport {!r}'.format(
|
|
||||||
fd, transport))
|
|
||||||
|
|
||||||
def add_reader(self, fd, callback, *args):
|
|
||||||
"""Add a reader callback."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._add_reader(fd, callback, *args)
|
|
||||||
|
|
||||||
def remove_reader(self, fd):
|
|
||||||
"""Remove a reader callback."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._remove_reader(fd)
|
|
||||||
|
|
||||||
def add_writer(self, fd, callback, *args):
|
|
||||||
"""Add a writer callback.."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._add_writer(fd, callback, *args)
|
|
||||||
|
|
||||||
def remove_writer(self, fd):
|
|
||||||
"""Remove a writer callback."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._remove_writer(fd)
|
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
self.remove_reader_count = collections.defaultdict(int)
|
|
||||||
self.remove_writer_count = collections.defaultdict(int)
|
|
||||||
|
|
||||||
def _run_once(self):
|
|
||||||
super()._run_once()
|
|
||||||
for when in self._timers:
|
|
||||||
advance = self._gen.send(when)
|
|
||||||
self.advance_time(advance)
|
|
||||||
self._timers = []
|
|
||||||
|
|
||||||
def call_at(self, when, callback, *args):
|
|
||||||
self._timers.append(when)
|
|
||||||
return super().call_at(when, callback, *args)
|
|
||||||
|
|
||||||
def _process_events(self, event_list):
|
|
||||||
return
|
|
||||||
|
|
||||||
def _write_to_self(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def MockCallback(**kwargs):
|
|
||||||
return mock.Mock(spec=['__call__'], **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MockPattern(str):
|
|
||||||
"""A regex based str with a fuzzy __eq__.
|
|
||||||
|
|
||||||
Use this helper with 'mock.assert_called_with', or anywhere
|
|
||||||
where a regex comparison between strings is needed.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
mock_call.assert_called_with(MockPattern('spam.*ham'))
|
|
||||||
"""
|
|
||||||
def __eq__(self, other):
|
|
||||||
return bool(re.search(str(self), other, re.S))
|
|
||||||
|
|
||||||
|
|
||||||
def get_function_source(func):
|
|
||||||
source = events._get_function_source(func)
|
|
||||||
if source is None:
|
|
||||||
raise ValueError("unable to get the source of %r" % (func,))
|
|
||||||
return source
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
|
||||||
def set_event_loop(self, loop, *, cleanup=True):
|
|
||||||
assert loop is not None
|
|
||||||
# ensure that the event loop is passed explicitly in asyncio
|
|
||||||
events.set_event_loop(None)
|
|
||||||
if cleanup:
|
|
||||||
self.addCleanup(loop.close)
|
|
||||||
|
|
||||||
def new_test_loop(self, gen=None):
|
|
||||||
loop = TestLoop(gen)
|
|
||||||
self.set_event_loop(loop)
|
|
||||||
return loop
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self._get_running_loop = events._get_running_loop
|
|
||||||
events._get_running_loop = lambda: None
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
events._get_running_loop = self._get_running_loop
|
|
||||||
|
|
||||||
events.set_event_loop(None)
|
|
||||||
|
|
||||||
# Detect CPython bug #23353: ensure that yield/yield-from is not used
|
|
||||||
# in an except block of a generator
|
|
||||||
self.assertEqual(sys.exc_info(), (None, None, None))
|
|
||||||
|
|
||||||
if not compat.PY34:
|
|
||||||
# Python 3.3 compatibility
|
|
||||||
def subTest(self, *args, **kwargs):
|
|
||||||
class EmptyCM:
|
|
||||||
def __enter__(self):
|
|
||||||
pass
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
pass
|
|
||||||
return EmptyCM()
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def disable_logger():
|
|
||||||
"""Context manager to disable asyncio logger.
|
|
||||||
|
|
||||||
For example, it can be used to ignore warnings in debug mode.
|
|
||||||
"""
|
|
||||||
old_level = logger.level
|
|
||||||
try:
|
|
||||||
logger.setLevel(logging.CRITICAL+1)
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
logger.setLevel(old_level)
|
|
||||||
|
|
||||||
|
|
||||||
def mock_nonblocking_socket(proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM,
|
|
||||||
family=socket.AF_INET):
|
|
||||||
"""Create a mock of a non-blocking socket."""
|
|
||||||
sock = mock.MagicMock(socket.socket)
|
|
||||||
sock.proto = proto
|
|
||||||
sock.type = type
|
|
||||||
sock.family = family
|
|
||||||
sock.gettimeout.return_value = 0.0
|
|
||||||
return sock
|
|
||||||
|
|
||||||
|
|
||||||
def force_legacy_ssl_support():
|
|
||||||
return mock.patch('asyncio.sslproto._is_sslproto_available',
|
|
||||||
return_value=False)
|
|
||||||
25
Lib/asyncio/threads.py
vendored
Normal file
25
Lib/asyncio/threads.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""High-level support for working with threads in asyncio"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import contextvars
|
||||||
|
|
||||||
|
from . import events
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = "to_thread",
|
||||||
|
|
||||||
|
|
||||||
|
async def to_thread(func, /, *args, **kwargs):
|
||||||
|
"""Asynchronously run function *func* in a separate thread.
|
||||||
|
|
||||||
|
Any *args and **kwargs supplied for this function are directly passed
|
||||||
|
to *func*. Also, the current :class:`contextvars.Context` is propagated,
|
||||||
|
allowing context variables from the main thread to be accessed in the
|
||||||
|
separate thread.
|
||||||
|
|
||||||
|
Return a coroutine that can be awaited to get the eventual result of *func*.
|
||||||
|
"""
|
||||||
|
loop = events.get_running_loop()
|
||||||
|
ctx = contextvars.copy_context()
|
||||||
|
func_call = functools.partial(ctx.run, func, *args, **kwargs)
|
||||||
|
return await loop.run_in_executor(None, func_call)
|
||||||
168
Lib/asyncio/timeouts.py
vendored
Normal file
168
Lib/asyncio/timeouts.py
vendored
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
from types import TracebackType
|
||||||
|
from typing import final, Optional, Type
|
||||||
|
|
||||||
|
from . import events
|
||||||
|
from . import exceptions
|
||||||
|
from . import tasks
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Timeout",
|
||||||
|
"timeout",
|
||||||
|
"timeout_at",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _State(enum.Enum):
|
||||||
|
CREATED = "created"
|
||||||
|
ENTERED = "active"
|
||||||
|
EXPIRING = "expiring"
|
||||||
|
EXPIRED = "expired"
|
||||||
|
EXITED = "finished"
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Timeout:
|
||||||
|
"""Asynchronous context manager for cancelling overdue coroutines.
|
||||||
|
|
||||||
|
Use `timeout()` or `timeout_at()` rather than instantiating this class directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, when: Optional[float]) -> None:
|
||||||
|
"""Schedule a timeout that will trigger at a given loop time.
|
||||||
|
|
||||||
|
- If `when` is `None`, the timeout will never trigger.
|
||||||
|
- If `when < loop.time()`, the timeout will trigger on the next
|
||||||
|
iteration of the event loop.
|
||||||
|
"""
|
||||||
|
self._state = _State.CREATED
|
||||||
|
|
||||||
|
self._timeout_handler: Optional[events.TimerHandle] = None
|
||||||
|
self._task: Optional[tasks.Task] = None
|
||||||
|
self._when = when
|
||||||
|
|
||||||
|
def when(self) -> Optional[float]:
|
||||||
|
"""Return the current deadline."""
|
||||||
|
return self._when
|
||||||
|
|
||||||
|
def reschedule(self, when: Optional[float]) -> None:
|
||||||
|
"""Reschedule the timeout."""
|
||||||
|
if self._state is not _State.ENTERED:
|
||||||
|
if self._state is _State.CREATED:
|
||||||
|
raise RuntimeError("Timeout has not been entered")
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Cannot change state of {self._state.value} Timeout",
|
||||||
|
)
|
||||||
|
|
||||||
|
self._when = when
|
||||||
|
|
||||||
|
if self._timeout_handler is not None:
|
||||||
|
self._timeout_handler.cancel()
|
||||||
|
|
||||||
|
if when is None:
|
||||||
|
self._timeout_handler = None
|
||||||
|
else:
|
||||||
|
loop = events.get_running_loop()
|
||||||
|
if when <= loop.time():
|
||||||
|
self._timeout_handler = loop.call_soon(self._on_timeout)
|
||||||
|
else:
|
||||||
|
self._timeout_handler = loop.call_at(when, self._on_timeout)
|
||||||
|
|
||||||
|
def expired(self) -> bool:
|
||||||
|
"""Is timeout expired during execution?"""
|
||||||
|
return self._state in (_State.EXPIRING, _State.EXPIRED)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
info = ['']
|
||||||
|
if self._state is _State.ENTERED:
|
||||||
|
when = round(self._when, 3) if self._when is not None else None
|
||||||
|
info.append(f"when={when}")
|
||||||
|
info_str = ' '.join(info)
|
||||||
|
return f"<Timeout [{self._state.value}]{info_str}>"
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "Timeout":
|
||||||
|
if self._state is not _State.CREATED:
|
||||||
|
raise RuntimeError("Timeout has already been entered")
|
||||||
|
task = tasks.current_task()
|
||||||
|
if task is None:
|
||||||
|
raise RuntimeError("Timeout should be used inside a task")
|
||||||
|
self._state = _State.ENTERED
|
||||||
|
self._task = task
|
||||||
|
self._cancelling = self._task.cancelling()
|
||||||
|
self.reschedule(self._when)
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(
|
||||||
|
self,
|
||||||
|
exc_type: Optional[Type[BaseException]],
|
||||||
|
exc_val: Optional[BaseException],
|
||||||
|
exc_tb: Optional[TracebackType],
|
||||||
|
) -> Optional[bool]:
|
||||||
|
assert self._state in (_State.ENTERED, _State.EXPIRING)
|
||||||
|
|
||||||
|
if self._timeout_handler is not None:
|
||||||
|
self._timeout_handler.cancel()
|
||||||
|
self._timeout_handler = None
|
||||||
|
|
||||||
|
if self._state is _State.EXPIRING:
|
||||||
|
self._state = _State.EXPIRED
|
||||||
|
|
||||||
|
if self._task.uncancel() <= self._cancelling and exc_type is exceptions.CancelledError:
|
||||||
|
# Since there are no new cancel requests, we're
|
||||||
|
# handling this.
|
||||||
|
raise TimeoutError from exc_val
|
||||||
|
elif self._state is _State.ENTERED:
|
||||||
|
self._state = _State.EXITED
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _on_timeout(self) -> None:
|
||||||
|
assert self._state is _State.ENTERED
|
||||||
|
self._task.cancel()
|
||||||
|
self._state = _State.EXPIRING
|
||||||
|
# drop the reference early
|
||||||
|
self._timeout_handler = None
|
||||||
|
|
||||||
|
|
||||||
|
def timeout(delay: Optional[float]) -> Timeout:
|
||||||
|
"""Timeout async context manager.
|
||||||
|
|
||||||
|
Useful in cases when you want to apply timeout logic around block
|
||||||
|
of code or in cases when asyncio.wait_for is not suitable. For example:
|
||||||
|
|
||||||
|
>>> async with asyncio.timeout(10): # 10 seconds timeout
|
||||||
|
... await long_running_task()
|
||||||
|
|
||||||
|
|
||||||
|
delay - value in seconds or None to disable timeout logic
|
||||||
|
|
||||||
|
long_running_task() is interrupted by raising asyncio.CancelledError,
|
||||||
|
the top-most affected timeout() context manager converts CancelledError
|
||||||
|
into TimeoutError.
|
||||||
|
"""
|
||||||
|
loop = events.get_running_loop()
|
||||||
|
return Timeout(loop.time() + delay if delay is not None else None)
|
||||||
|
|
||||||
|
|
||||||
|
def timeout_at(when: Optional[float]) -> Timeout:
|
||||||
|
"""Schedule the timeout at absolute time.
|
||||||
|
|
||||||
|
Like timeout() but argument gives absolute time in the same clock system
|
||||||
|
as loop.time().
|
||||||
|
|
||||||
|
Please note: it is not POSIX time but a time with
|
||||||
|
undefined starting base, e.g. the time of the system power on.
|
||||||
|
|
||||||
|
>>> async with asyncio.timeout_at(loop.time() + 10):
|
||||||
|
... await long_running_task()
|
||||||
|
|
||||||
|
|
||||||
|
when - a deadline when timeout occurs or None to disable timeout logic
|
||||||
|
|
||||||
|
long_running_task() is interrupted by raising asyncio.CancelledError,
|
||||||
|
the top-most affected timeout() context manager converts CancelledError
|
||||||
|
into TimeoutError.
|
||||||
|
"""
|
||||||
|
return Timeout(when)
|
||||||
59
Lib/asyncio/transports.py
vendored
59
Lib/asyncio/transports.py
vendored
@@ -1,15 +1,16 @@
|
|||||||
"""Abstract Transport class."""
|
"""Abstract Transport class."""
|
||||||
|
|
||||||
from asyncio import compat
|
__all__ = (
|
||||||
|
'BaseTransport', 'ReadTransport', 'WriteTransport',
|
||||||
__all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport',
|
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
||||||
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
)
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTransport:
|
class BaseTransport:
|
||||||
"""Base class for transports."""
|
"""Base class for transports."""
|
||||||
|
|
||||||
|
__slots__ = ('_extra',)
|
||||||
|
|
||||||
def __init__(self, extra=None):
|
def __init__(self, extra=None):
|
||||||
if extra is None:
|
if extra is None:
|
||||||
extra = {}
|
extra = {}
|
||||||
@@ -28,8 +29,8 @@ class BaseTransport:
|
|||||||
|
|
||||||
Buffered data will be flushed asynchronously. No more data
|
Buffered data will be flushed asynchronously. No more data
|
||||||
will be received. After all buffered data is flushed, the
|
will be received. After all buffered data is flushed, the
|
||||||
protocol's connection_lost() method will (eventually) called
|
protocol's connection_lost() method will (eventually) be
|
||||||
with None as its argument.
|
called with None as its argument.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -45,6 +46,12 @@ class BaseTransport:
|
|||||||
class ReadTransport(BaseTransport):
|
class ReadTransport(BaseTransport):
|
||||||
"""Interface for read-only transports."""
|
"""Interface for read-only transports."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def is_reading(self):
|
||||||
|
"""Return True if the transport is receiving."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def pause_reading(self):
|
def pause_reading(self):
|
||||||
"""Pause the receiving end.
|
"""Pause the receiving end.
|
||||||
|
|
||||||
@@ -65,6 +72,8 @@ class ReadTransport(BaseTransport):
|
|||||||
class WriteTransport(BaseTransport):
|
class WriteTransport(BaseTransport):
|
||||||
"""Interface for write-only transports."""
|
"""Interface for write-only transports."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def set_write_buffer_limits(self, high=None, low=None):
|
def set_write_buffer_limits(self, high=None, low=None):
|
||||||
"""Set the high- and low-water limits for write flow control.
|
"""Set the high- and low-water limits for write flow control.
|
||||||
|
|
||||||
@@ -90,6 +99,12 @@ class WriteTransport(BaseTransport):
|
|||||||
"""Return the current size of the write buffer."""
|
"""Return the current size of the write buffer."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_write_buffer_limits(self):
|
||||||
|
"""Get the high and low watermarks for write flow control.
|
||||||
|
Return a tuple (low, high) where low and high are
|
||||||
|
positive number of bytes."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
"""Write some data bytes to the transport.
|
"""Write some data bytes to the transport.
|
||||||
|
|
||||||
@@ -104,7 +119,7 @@ class WriteTransport(BaseTransport):
|
|||||||
The default implementation concatenates the arguments and
|
The default implementation concatenates the arguments and
|
||||||
calls write() on the result.
|
calls write() on the result.
|
||||||
"""
|
"""
|
||||||
data = compat.flatten_list_bytes(list_of_data)
|
data = b''.join(list_of_data)
|
||||||
self.write(data)
|
self.write(data)
|
||||||
|
|
||||||
def write_eof(self):
|
def write_eof(self):
|
||||||
@@ -151,10 +166,14 @@ class Transport(ReadTransport, WriteTransport):
|
|||||||
except writelines(), which calls write() in a loop.
|
except writelines(), which calls write() in a loop.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
||||||
class DatagramTransport(BaseTransport):
|
class DatagramTransport(BaseTransport):
|
||||||
"""Interface for datagram (UDP) transports."""
|
"""Interface for datagram (UDP) transports."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def sendto(self, data, addr=None):
|
def sendto(self, data, addr=None):
|
||||||
"""Send data to the transport.
|
"""Send data to the transport.
|
||||||
|
|
||||||
@@ -177,6 +196,8 @@ class DatagramTransport(BaseTransport):
|
|||||||
|
|
||||||
class SubprocessTransport(BaseTransport):
|
class SubprocessTransport(BaseTransport):
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def get_pid(self):
|
def get_pid(self):
|
||||||
"""Get subprocess id."""
|
"""Get subprocess id."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -244,6 +265,8 @@ class _FlowControlMixin(Transport):
|
|||||||
resume_writing() may be called.
|
resume_writing() may be called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_loop', '_protocol_paused', '_high_water', '_low_water')
|
||||||
|
|
||||||
def __init__(self, extra=None, loop=None):
|
def __init__(self, extra=None, loop=None):
|
||||||
super().__init__(extra)
|
super().__init__(extra)
|
||||||
assert loop is not None
|
assert loop is not None
|
||||||
@@ -259,7 +282,9 @@ class _FlowControlMixin(Transport):
|
|||||||
self._protocol_paused = True
|
self._protocol_paused = True
|
||||||
try:
|
try:
|
||||||
self._protocol.pause_writing()
|
self._protocol.pause_writing()
|
||||||
except Exception as exc:
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
self._loop.call_exception_handler({
|
self._loop.call_exception_handler({
|
||||||
'message': 'protocol.pause_writing() failed',
|
'message': 'protocol.pause_writing() failed',
|
||||||
'exception': exc,
|
'exception': exc,
|
||||||
@@ -269,11 +294,13 @@ class _FlowControlMixin(Transport):
|
|||||||
|
|
||||||
def _maybe_resume_protocol(self):
|
def _maybe_resume_protocol(self):
|
||||||
if (self._protocol_paused and
|
if (self._protocol_paused and
|
||||||
self.get_write_buffer_size() <= self._low_water):
|
self.get_write_buffer_size() <= self._low_water):
|
||||||
self._protocol_paused = False
|
self._protocol_paused = False
|
||||||
try:
|
try:
|
||||||
self._protocol.resume_writing()
|
self._protocol.resume_writing()
|
||||||
except Exception as exc:
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except BaseException as exc:
|
||||||
self._loop.call_exception_handler({
|
self._loop.call_exception_handler({
|
||||||
'message': 'protocol.resume_writing() failed',
|
'message': 'protocol.resume_writing() failed',
|
||||||
'exception': exc,
|
'exception': exc,
|
||||||
@@ -287,14 +314,16 @@ class _FlowControlMixin(Transport):
|
|||||||
def _set_write_buffer_limits(self, high=None, low=None):
|
def _set_write_buffer_limits(self, high=None, low=None):
|
||||||
if high is None:
|
if high is None:
|
||||||
if low is None:
|
if low is None:
|
||||||
high = 64*1024
|
high = 64 * 1024
|
||||||
else:
|
else:
|
||||||
high = 4*low
|
high = 4 * low
|
||||||
if low is None:
|
if low is None:
|
||||||
low = high // 4
|
low = high // 4
|
||||||
|
|
||||||
if not high >= low >= 0:
|
if not high >= low >= 0:
|
||||||
raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
|
raise ValueError(
|
||||||
(high, low))
|
f'high ({high!r}) must be >= low ({low!r}) must be >= 0')
|
||||||
|
|
||||||
self._high_water = high
|
self._high_water = high
|
||||||
self._low_water = low
|
self._low_water = low
|
||||||
|
|
||||||
|
|||||||
98
Lib/asyncio/trsock.py
vendored
Normal file
98
Lib/asyncio/trsock.py
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
class TransportSocket:
|
||||||
|
|
||||||
|
"""A socket-like wrapper for exposing real transport sockets.
|
||||||
|
|
||||||
|
These objects can be safely returned by APIs like
|
||||||
|
`transport.get_extra_info('socket')`. All potentially disruptive
|
||||||
|
operations (like "socket.close()") are banned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_sock',)
|
||||||
|
|
||||||
|
def __init__(self, sock: socket.socket):
|
||||||
|
self._sock = sock
|
||||||
|
|
||||||
|
@property
|
||||||
|
def family(self):
|
||||||
|
return self._sock.family
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return self._sock.type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proto(self):
|
||||||
|
return self._sock.proto
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = (
|
||||||
|
f"<asyncio.TransportSocket fd={self.fileno()}, "
|
||||||
|
f"family={self.family!s}, type={self.type!s}, "
|
||||||
|
f"proto={self.proto}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.fileno() != -1:
|
||||||
|
try:
|
||||||
|
laddr = self.getsockname()
|
||||||
|
if laddr:
|
||||||
|
s = f"{s}, laddr={laddr}"
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
raddr = self.getpeername()
|
||||||
|
if raddr:
|
||||||
|
s = f"{s}, raddr={raddr}"
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return f"{s}>"
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
raise TypeError("Cannot serialize asyncio.TransportSocket object")
|
||||||
|
|
||||||
|
def fileno(self):
|
||||||
|
return self._sock.fileno()
|
||||||
|
|
||||||
|
def dup(self):
|
||||||
|
return self._sock.dup()
|
||||||
|
|
||||||
|
def get_inheritable(self):
|
||||||
|
return self._sock.get_inheritable()
|
||||||
|
|
||||||
|
def shutdown(self, how):
|
||||||
|
# asyncio doesn't currently provide a high-level transport API
|
||||||
|
# to shutdown the connection.
|
||||||
|
self._sock.shutdown(how)
|
||||||
|
|
||||||
|
def getsockopt(self, *args, **kwargs):
|
||||||
|
return self._sock.getsockopt(*args, **kwargs)
|
||||||
|
|
||||||
|
def setsockopt(self, *args, **kwargs):
|
||||||
|
self._sock.setsockopt(*args, **kwargs)
|
||||||
|
|
||||||
|
def getpeername(self):
|
||||||
|
return self._sock.getpeername()
|
||||||
|
|
||||||
|
def getsockname(self):
|
||||||
|
return self._sock.getsockname()
|
||||||
|
|
||||||
|
def getsockbyname(self):
|
||||||
|
return self._sock.getsockbyname()
|
||||||
|
|
||||||
|
def settimeout(self, value):
|
||||||
|
if value == 0:
|
||||||
|
return
|
||||||
|
raise ValueError(
|
||||||
|
'settimeout(): only 0 timeout is allowed on transport sockets')
|
||||||
|
|
||||||
|
def gettimeout(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def setblocking(self, flag):
|
||||||
|
if not flag:
|
||||||
|
return
|
||||||
|
raise ValueError(
|
||||||
|
'setblocking(): transport sockets cannot be blocking')
|
||||||
786
Lib/asyncio/unix_events.py
vendored
786
Lib/asyncio/unix_events.py
vendored
File diff suppressed because it is too large
Load Diff
289
Lib/asyncio/windows_events.py
vendored
289
Lib/asyncio/windows_events.py
vendored
@@ -1,32 +1,41 @@
|
|||||||
"""Selector and proactor event loops for Windows."""
|
"""Selector and proactor event loops for Windows."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.platform != 'win32': # pragma: no cover
|
||||||
|
raise ImportError('win32 only')
|
||||||
|
|
||||||
|
import _overlapped
|
||||||
import _winapi
|
import _winapi
|
||||||
import errno
|
import errno
|
||||||
|
from functools import partial
|
||||||
import math
|
import math
|
||||||
|
import msvcrt
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
|
import time
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from . import events
|
from . import events
|
||||||
from . import base_subprocess
|
from . import base_subprocess
|
||||||
from . import futures
|
from . import futures
|
||||||
|
from . import exceptions
|
||||||
from . import proactor_events
|
from . import proactor_events
|
||||||
from . import selector_events
|
from . import selector_events
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from . import windows_utils
|
from . import windows_utils
|
||||||
# XXX RustPython TODO: _overlapped
|
|
||||||
# from . import _overlapped
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
|
__all__ = (
|
||||||
'DefaultEventLoopPolicy',
|
'SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
|
||||||
]
|
'DefaultEventLoopPolicy', 'WindowsSelectorEventLoopPolicy',
|
||||||
|
'WindowsProactorEventLoopPolicy',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
NULL = 0
|
NULL = _winapi.NULL
|
||||||
INFINITE = 0xffffffff
|
INFINITE = _winapi.INFINITE
|
||||||
ERROR_CONNECTION_REFUSED = 1225
|
ERROR_CONNECTION_REFUSED = 1225
|
||||||
ERROR_CONNECTION_ABORTED = 1236
|
ERROR_CONNECTION_ABORTED = 1236
|
||||||
|
|
||||||
@@ -53,7 +62,7 @@ class _OverlappedFuture(futures.Future):
|
|||||||
info = super()._repr_info()
|
info = super()._repr_info()
|
||||||
if self._ov is not None:
|
if self._ov is not None:
|
||||||
state = 'pending' if self._ov.pending else 'completed'
|
state = 'pending' if self._ov.pending else 'completed'
|
||||||
info.insert(1, 'overlapped=<%s, %#x>' % (state, self._ov.address))
|
info.insert(1, f'overlapped=<{state}, {self._ov.address:#x}>')
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def _cancel_overlapped(self):
|
def _cancel_overlapped(self):
|
||||||
@@ -72,9 +81,9 @@ class _OverlappedFuture(futures.Future):
|
|||||||
self._loop.call_exception_handler(context)
|
self._loop.call_exception_handler(context)
|
||||||
self._ov = None
|
self._ov = None
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self, msg=None):
|
||||||
self._cancel_overlapped()
|
self._cancel_overlapped()
|
||||||
return super().cancel()
|
return super().cancel(msg=msg)
|
||||||
|
|
||||||
def set_exception(self, exception):
|
def set_exception(self, exception):
|
||||||
super().set_exception(exception)
|
super().set_exception(exception)
|
||||||
@@ -109,12 +118,12 @@ class _BaseWaitHandleFuture(futures.Future):
|
|||||||
|
|
||||||
def _repr_info(self):
|
def _repr_info(self):
|
||||||
info = super()._repr_info()
|
info = super()._repr_info()
|
||||||
info.append('handle=%#x' % self._handle)
|
info.append(f'handle={self._handle:#x}')
|
||||||
if self._handle is not None:
|
if self._handle is not None:
|
||||||
state = 'signaled' if self._poll() else 'waiting'
|
state = 'signaled' if self._poll() else 'waiting'
|
||||||
info.append(state)
|
info.append(state)
|
||||||
if self._wait_handle is not None:
|
if self._wait_handle is not None:
|
||||||
info.append('wait_handle=%#x' % self._wait_handle)
|
info.append(f'wait_handle={self._wait_handle:#x}')
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def _unregister_wait_cb(self, fut):
|
def _unregister_wait_cb(self, fut):
|
||||||
@@ -146,9 +155,9 @@ class _BaseWaitHandleFuture(futures.Future):
|
|||||||
|
|
||||||
self._unregister_wait_cb(None)
|
self._unregister_wait_cb(None)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self, msg=None):
|
||||||
self._unregister_wait()
|
self._unregister_wait()
|
||||||
return super().cancel()
|
return super().cancel(msg=msg)
|
||||||
|
|
||||||
def set_exception(self, exception):
|
def set_exception(self, exception):
|
||||||
self._unregister_wait()
|
self._unregister_wait()
|
||||||
@@ -297,9 +306,6 @@ class PipeServer(object):
|
|||||||
class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
"""Windows version of selector event loop."""
|
"""Windows version of selector event loop."""
|
||||||
|
|
||||||
def _socketpair(self):
|
|
||||||
return windows_utils.socketpair()
|
|
||||||
|
|
||||||
|
|
||||||
class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||||
"""Windows version of proactor event loop using IOCP."""
|
"""Windows version of proactor event loop using IOCP."""
|
||||||
@@ -309,20 +315,34 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
|||||||
proactor = IocpProactor()
|
proactor = IocpProactor()
|
||||||
super().__init__(proactor)
|
super().__init__(proactor)
|
||||||
|
|
||||||
def _socketpair(self):
|
def run_forever(self):
|
||||||
return windows_utils.socketpair()
|
try:
|
||||||
|
assert self._self_reading_future is None
|
||||||
|
self.call_soon(self._loop_self_reading)
|
||||||
|
super().run_forever()
|
||||||
|
finally:
|
||||||
|
if self._self_reading_future is not None:
|
||||||
|
ov = self._self_reading_future._ov
|
||||||
|
self._self_reading_future.cancel()
|
||||||
|
# self_reading_future always uses IOCP, so even though it's
|
||||||
|
# been cancelled, we need to make sure that the IOCP message
|
||||||
|
# is received so that the kernel is not holding on to the
|
||||||
|
# memory, possibly causing memory corruption later. Only
|
||||||
|
# unregister it if IO is complete in all respects. Otherwise
|
||||||
|
# we need another _poll() later to complete the IO.
|
||||||
|
if ov is not None and not ov.pending:
|
||||||
|
self._proactor._unregister(ov)
|
||||||
|
self._self_reading_future = None
|
||||||
|
|
||||||
@coroutine
|
async def create_pipe_connection(self, protocol_factory, address):
|
||||||
def create_pipe_connection(self, protocol_factory, address):
|
|
||||||
f = self._proactor.connect_pipe(address)
|
f = self._proactor.connect_pipe(address)
|
||||||
pipe = yield from f
|
pipe = await f
|
||||||
protocol = protocol_factory()
|
protocol = protocol_factory()
|
||||||
trans = self._make_duplex_pipe_transport(pipe, protocol,
|
trans = self._make_duplex_pipe_transport(pipe, protocol,
|
||||||
extra={'addr': address})
|
extra={'addr': address})
|
||||||
return trans, protocol
|
return trans, protocol
|
||||||
|
|
||||||
@coroutine
|
async def start_serving_pipe(self, protocol_factory, address):
|
||||||
def start_serving_pipe(self, protocol_factory, address):
|
|
||||||
server = PipeServer(address)
|
server = PipeServer(address)
|
||||||
|
|
||||||
def loop_accept_pipe(f=None):
|
def loop_accept_pipe(f=None):
|
||||||
@@ -347,6 +367,10 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
|||||||
return
|
return
|
||||||
|
|
||||||
f = self._proactor.accept_pipe(pipe)
|
f = self._proactor.accept_pipe(pipe)
|
||||||
|
except BrokenPipeError:
|
||||||
|
if pipe and pipe.fileno() != -1:
|
||||||
|
pipe.close()
|
||||||
|
self.call_soon(loop_accept_pipe)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
if pipe and pipe.fileno() != -1:
|
if pipe and pipe.fileno() != -1:
|
||||||
self.call_exception_handler({
|
self.call_exception_handler({
|
||||||
@@ -358,7 +382,8 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
|||||||
elif self._debug:
|
elif self._debug:
|
||||||
logger.warning("Accept pipe failed on pipe %r",
|
logger.warning("Accept pipe failed on pipe %r",
|
||||||
pipe, exc_info=True)
|
pipe, exc_info=True)
|
||||||
except futures.CancelledError:
|
self.call_soon(loop_accept_pipe)
|
||||||
|
except exceptions.CancelledError:
|
||||||
if pipe:
|
if pipe:
|
||||||
pipe.close()
|
pipe.close()
|
||||||
else:
|
else:
|
||||||
@@ -368,28 +393,22 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
|||||||
self.call_soon(loop_accept_pipe)
|
self.call_soon(loop_accept_pipe)
|
||||||
return [server]
|
return [server]
|
||||||
|
|
||||||
@coroutine
|
async def _make_subprocess_transport(self, protocol, args, shell,
|
||||||
def _make_subprocess_transport(self, protocol, args, shell,
|
stdin, stdout, stderr, bufsize,
|
||||||
stdin, stdout, stderr, bufsize,
|
extra=None, **kwargs):
|
||||||
extra=None, **kwargs):
|
|
||||||
waiter = self.create_future()
|
waiter = self.create_future()
|
||||||
transp = _WindowsSubprocessTransport(self, protocol, args, shell,
|
transp = _WindowsSubprocessTransport(self, protocol, args, shell,
|
||||||
stdin, stdout, stderr, bufsize,
|
stdin, stdout, stderr, bufsize,
|
||||||
waiter=waiter, extra=extra,
|
waiter=waiter, extra=extra,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
try:
|
try:
|
||||||
yield from waiter
|
await waiter
|
||||||
except Exception as exc:
|
except (SystemExit, KeyboardInterrupt):
|
||||||
# Workaround CPython bug #23353: using yield/yield-from in an
|
raise
|
||||||
# except block of a generator doesn't clear properly sys.exc_info()
|
except BaseException:
|
||||||
err = exc
|
|
||||||
else:
|
|
||||||
err = None
|
|
||||||
|
|
||||||
if err is not None:
|
|
||||||
transp.close()
|
transp.close()
|
||||||
yield from transp._wait()
|
await transp._wait()
|
||||||
raise err
|
raise
|
||||||
|
|
||||||
return transp
|
return transp
|
||||||
|
|
||||||
@@ -397,7 +416,7 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
|||||||
class IocpProactor:
|
class IocpProactor:
|
||||||
"""Proactor implementation using IOCP."""
|
"""Proactor implementation using IOCP."""
|
||||||
|
|
||||||
def __init__(self, concurrency=0xffffffff):
|
def __init__(self, concurrency=INFINITE):
|
||||||
self._loop = None
|
self._loop = None
|
||||||
self._results = []
|
self._results = []
|
||||||
self._iocp = _overlapped.CreateIoCompletionPort(
|
self._iocp = _overlapped.CreateIoCompletionPort(
|
||||||
@@ -407,10 +426,16 @@ class IocpProactor:
|
|||||||
self._unregistered = []
|
self._unregistered = []
|
||||||
self._stopped_serving = weakref.WeakSet()
|
self._stopped_serving = weakref.WeakSet()
|
||||||
|
|
||||||
|
def _check_closed(self):
|
||||||
|
if self._iocp is None:
|
||||||
|
raise RuntimeError('IocpProactor is closed')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('<%s overlapped#=%s result#=%s>'
|
info = ['overlapped#=%s' % len(self._cache),
|
||||||
% (self.__class__.__name__, len(self._cache),
|
'result#=%s' % len(self._results)]
|
||||||
len(self._results)))
|
if self._iocp is None:
|
||||||
|
info.append('closed')
|
||||||
|
return '<%s %s>' % (self.__class__.__name__, " ".join(info))
|
||||||
|
|
||||||
def set_loop(self, loop):
|
def set_loop(self, loop):
|
||||||
self._loop = loop
|
self._loop = loop
|
||||||
@@ -420,13 +445,40 @@ class IocpProactor:
|
|||||||
self._poll(timeout)
|
self._poll(timeout)
|
||||||
tmp = self._results
|
tmp = self._results
|
||||||
self._results = []
|
self._results = []
|
||||||
return tmp
|
try:
|
||||||
|
return tmp
|
||||||
|
finally:
|
||||||
|
# Needed to break cycles when an exception occurs.
|
||||||
|
tmp = None
|
||||||
|
|
||||||
def _result(self, value):
|
def _result(self, value):
|
||||||
fut = self._loop.create_future()
|
fut = self._loop.create_future()
|
||||||
fut.set_result(value)
|
fut.set_result(value)
|
||||||
return fut
|
return fut
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def finish_socket_func(trans, key, ov):
|
||||||
|
try:
|
||||||
|
return ov.getresult()
|
||||||
|
except OSError as exc:
|
||||||
|
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
|
||||||
|
_overlapped.ERROR_OPERATION_ABORTED):
|
||||||
|
raise ConnectionResetError(*exc.args)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _finish_recvfrom(cls, trans, key, ov, *, empty_result):
|
||||||
|
try:
|
||||||
|
return cls.finish_socket_func(trans, key, ov)
|
||||||
|
except OSError as exc:
|
||||||
|
# WSARecvFrom will report ERROR_PORT_UNREACHABLE when the same
|
||||||
|
# socket is used to send to an address that is not listening.
|
||||||
|
if exc.winerror == _overlapped.ERROR_PORT_UNREACHABLE:
|
||||||
|
return empty_result, None
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
def recv(self, conn, nbytes, flags=0):
|
def recv(self, conn, nbytes, flags=0):
|
||||||
self._register_with_iocp(conn)
|
self._register_with_iocp(conn)
|
||||||
ov = _overlapped.Overlapped(NULL)
|
ov = _overlapped.Overlapped(NULL)
|
||||||
@@ -438,16 +490,50 @@ class IocpProactor:
|
|||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
return self._result(b'')
|
return self._result(b'')
|
||||||
|
|
||||||
def finish_recv(trans, key, ov):
|
return self._register(ov, conn, self.finish_socket_func)
|
||||||
try:
|
|
||||||
return ov.getresult()
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
|
|
||||||
raise ConnectionResetError(*exc.args)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
return self._register(ov, conn, finish_recv)
|
def recv_into(self, conn, buf, flags=0):
|
||||||
|
self._register_with_iocp(conn)
|
||||||
|
ov = _overlapped.Overlapped(NULL)
|
||||||
|
try:
|
||||||
|
if isinstance(conn, socket.socket):
|
||||||
|
ov.WSARecvInto(conn.fileno(), buf, flags)
|
||||||
|
else:
|
||||||
|
ov.ReadFileInto(conn.fileno(), buf)
|
||||||
|
except BrokenPipeError:
|
||||||
|
return self._result(0)
|
||||||
|
|
||||||
|
return self._register(ov, conn, self.finish_socket_func)
|
||||||
|
|
||||||
|
def recvfrom(self, conn, nbytes, flags=0):
|
||||||
|
self._register_with_iocp(conn)
|
||||||
|
ov = _overlapped.Overlapped(NULL)
|
||||||
|
try:
|
||||||
|
ov.WSARecvFrom(conn.fileno(), nbytes, flags)
|
||||||
|
except BrokenPipeError:
|
||||||
|
return self._result((b'', None))
|
||||||
|
|
||||||
|
return self._register(ov, conn, partial(self._finish_recvfrom,
|
||||||
|
empty_result=b''))
|
||||||
|
|
||||||
|
def recvfrom_into(self, conn, buf, flags=0):
|
||||||
|
self._register_with_iocp(conn)
|
||||||
|
ov = _overlapped.Overlapped(NULL)
|
||||||
|
try:
|
||||||
|
ov.WSARecvFromInto(conn.fileno(), buf, flags)
|
||||||
|
except BrokenPipeError:
|
||||||
|
return self._result((0, None))
|
||||||
|
|
||||||
|
return self._register(ov, conn, partial(self._finish_recvfrom,
|
||||||
|
empty_result=0))
|
||||||
|
|
||||||
|
def sendto(self, conn, buf, flags=0, addr=None):
|
||||||
|
self._register_with_iocp(conn)
|
||||||
|
ov = _overlapped.Overlapped(NULL)
|
||||||
|
|
||||||
|
ov.WSASendTo(conn.fileno(), buf, flags, addr)
|
||||||
|
|
||||||
|
return self._register(ov, conn, self.finish_socket_func)
|
||||||
|
|
||||||
def send(self, conn, buf, flags=0):
|
def send(self, conn, buf, flags=0):
|
||||||
self._register_with_iocp(conn)
|
self._register_with_iocp(conn)
|
||||||
@@ -457,16 +543,7 @@ class IocpProactor:
|
|||||||
else:
|
else:
|
||||||
ov.WriteFile(conn.fileno(), buf)
|
ov.WriteFile(conn.fileno(), buf)
|
||||||
|
|
||||||
def finish_send(trans, key, ov):
|
return self._register(ov, conn, self.finish_socket_func)
|
||||||
try:
|
|
||||||
return ov.getresult()
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
|
|
||||||
raise ConnectionResetError(*exc.args)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
return self._register(ov, conn, finish_send)
|
|
||||||
|
|
||||||
def accept(self, listener):
|
def accept(self, listener):
|
||||||
self._register_with_iocp(listener)
|
self._register_with_iocp(listener)
|
||||||
@@ -483,12 +560,11 @@ class IocpProactor:
|
|||||||
conn.settimeout(listener.gettimeout())
|
conn.settimeout(listener.gettimeout())
|
||||||
return conn, conn.getpeername()
|
return conn, conn.getpeername()
|
||||||
|
|
||||||
@coroutine
|
async def accept_coro(future, conn):
|
||||||
def accept_coro(future, conn):
|
|
||||||
# Coroutine closing the accept socket if the future is cancelled
|
# Coroutine closing the accept socket if the future is cancelled
|
||||||
try:
|
try:
|
||||||
yield from future
|
await future
|
||||||
except futures.CancelledError:
|
except exceptions.CancelledError:
|
||||||
conn.close()
|
conn.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -498,6 +574,14 @@ class IocpProactor:
|
|||||||
return future
|
return future
|
||||||
|
|
||||||
def connect(self, conn, address):
|
def connect(self, conn, address):
|
||||||
|
if conn.type == socket.SOCK_DGRAM:
|
||||||
|
# WSAConnect will complete immediately for UDP sockets so we don't
|
||||||
|
# need to register any IOCP operation
|
||||||
|
_overlapped.WSAConnect(conn.fileno(), address)
|
||||||
|
fut = self._loop.create_future()
|
||||||
|
fut.set_result(None)
|
||||||
|
return fut
|
||||||
|
|
||||||
self._register_with_iocp(conn)
|
self._register_with_iocp(conn)
|
||||||
# The socket needs to be locally bound before we call ConnectEx().
|
# The socket needs to be locally bound before we call ConnectEx().
|
||||||
try:
|
try:
|
||||||
@@ -520,6 +604,18 @@ class IocpProactor:
|
|||||||
|
|
||||||
return self._register(ov, conn, finish_connect)
|
return self._register(ov, conn, finish_connect)
|
||||||
|
|
||||||
|
def sendfile(self, sock, file, offset, count):
|
||||||
|
self._register_with_iocp(sock)
|
||||||
|
ov = _overlapped.Overlapped(NULL)
|
||||||
|
offset_low = offset & 0xffff_ffff
|
||||||
|
offset_high = (offset >> 32) & 0xffff_ffff
|
||||||
|
ov.TransmitFile(sock.fileno(),
|
||||||
|
msvcrt.get_osfhandle(file.fileno()),
|
||||||
|
offset_low, offset_high,
|
||||||
|
count, 0, 0)
|
||||||
|
|
||||||
|
return self._register(ov, sock, self.finish_socket_func)
|
||||||
|
|
||||||
def accept_pipe(self, pipe):
|
def accept_pipe(self, pipe):
|
||||||
self._register_with_iocp(pipe)
|
self._register_with_iocp(pipe)
|
||||||
ov = _overlapped.Overlapped(NULL)
|
ov = _overlapped.Overlapped(NULL)
|
||||||
@@ -537,13 +633,12 @@ class IocpProactor:
|
|||||||
|
|
||||||
return self._register(ov, pipe, finish_accept_pipe)
|
return self._register(ov, pipe, finish_accept_pipe)
|
||||||
|
|
||||||
@coroutine
|
async def connect_pipe(self, address):
|
||||||
def connect_pipe(self, address):
|
|
||||||
delay = CONNECT_PIPE_INIT_DELAY
|
delay = CONNECT_PIPE_INIT_DELAY
|
||||||
while True:
|
while True:
|
||||||
# Unfortunately there is no way to do an overlapped connect to a pipe.
|
# Unfortunately there is no way to do an overlapped connect to
|
||||||
# Call CreateFile() in a loop until it doesn't fail with
|
# a pipe. Call CreateFile() in a loop until it doesn't fail with
|
||||||
# ERROR_PIPE_BUSY
|
# ERROR_PIPE_BUSY.
|
||||||
try:
|
try:
|
||||||
handle = _overlapped.ConnectPipe(address)
|
handle = _overlapped.ConnectPipe(address)
|
||||||
break
|
break
|
||||||
@@ -553,7 +648,7 @@ class IocpProactor:
|
|||||||
|
|
||||||
# ConnectPipe() failed with ERROR_PIPE_BUSY: retry later
|
# ConnectPipe() failed with ERROR_PIPE_BUSY: retry later
|
||||||
delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY)
|
delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY)
|
||||||
yield from tasks.sleep(delay, loop=self._loop)
|
await tasks.sleep(delay)
|
||||||
|
|
||||||
return windows_utils.PipeHandle(handle)
|
return windows_utils.PipeHandle(handle)
|
||||||
|
|
||||||
@@ -573,6 +668,8 @@ class IocpProactor:
|
|||||||
return fut
|
return fut
|
||||||
|
|
||||||
def _wait_for_handle(self, handle, timeout, _is_cancel):
|
def _wait_for_handle(self, handle, timeout, _is_cancel):
|
||||||
|
self._check_closed()
|
||||||
|
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
ms = _winapi.INFINITE
|
ms = _winapi.INFINITE
|
||||||
else:
|
else:
|
||||||
@@ -615,6 +712,8 @@ class IocpProactor:
|
|||||||
# that succeed immediately.
|
# that succeed immediately.
|
||||||
|
|
||||||
def _register(self, ov, obj, callback):
|
def _register(self, ov, obj, callback):
|
||||||
|
self._check_closed()
|
||||||
|
|
||||||
# Return a future which will be set with the result of the
|
# Return a future which will be set with the result of the
|
||||||
# operation when it completes. The future's value is actually
|
# operation when it completes. The future's value is actually
|
||||||
# the value returned by callback().
|
# the value returned by callback().
|
||||||
@@ -651,6 +750,7 @@ class IocpProactor:
|
|||||||
already be signalled (pending in the proactor event queue). It is also
|
already be signalled (pending in the proactor event queue). It is also
|
||||||
safe if the event is never signalled (because it was cancelled).
|
safe if the event is never signalled (because it was cancelled).
|
||||||
"""
|
"""
|
||||||
|
self._check_closed()
|
||||||
self._unregistered.append(ov)
|
self._unregistered.append(ov)
|
||||||
|
|
||||||
def _get_accept_socket(self, family):
|
def _get_accept_socket(self, family):
|
||||||
@@ -707,8 +807,10 @@ class IocpProactor:
|
|||||||
else:
|
else:
|
||||||
f.set_result(value)
|
f.set_result(value)
|
||||||
self._results.append(f)
|
self._results.append(f)
|
||||||
|
finally:
|
||||||
|
f = None
|
||||||
|
|
||||||
# Remove unregisted futures
|
# Remove unregistered futures
|
||||||
for ov in self._unregistered:
|
for ov in self._unregistered:
|
||||||
self._cache.pop(ov.address, None)
|
self._cache.pop(ov.address, None)
|
||||||
self._unregistered.clear()
|
self._unregistered.clear()
|
||||||
@@ -720,8 +822,12 @@ class IocpProactor:
|
|||||||
self._stopped_serving.add(obj)
|
self._stopped_serving.add(obj)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
if self._iocp is None:
|
||||||
|
# already closed
|
||||||
|
return
|
||||||
|
|
||||||
# Cancel remaining registered operations.
|
# Cancel remaining registered operations.
|
||||||
for address, (fut, ov, obj, callback) in list(self._cache.items()):
|
for fut, ov, obj, callback in list(self._cache.values()):
|
||||||
if fut.cancelled():
|
if fut.cancelled():
|
||||||
# Nothing to do with cancelled futures
|
# Nothing to do with cancelled futures
|
||||||
pass
|
pass
|
||||||
@@ -742,14 +848,25 @@ class IocpProactor:
|
|||||||
context['source_traceback'] = fut._source_traceback
|
context['source_traceback'] = fut._source_traceback
|
||||||
self._loop.call_exception_handler(context)
|
self._loop.call_exception_handler(context)
|
||||||
|
|
||||||
|
# Wait until all cancelled overlapped complete: don't exit with running
|
||||||
|
# overlapped to prevent a crash. Display progress every second if the
|
||||||
|
# loop is still running.
|
||||||
|
msg_update = 1.0
|
||||||
|
start_time = time.monotonic()
|
||||||
|
next_msg = start_time + msg_update
|
||||||
while self._cache:
|
while self._cache:
|
||||||
if not self._poll(1):
|
if next_msg <= time.monotonic():
|
||||||
logger.debug('taking long time to close proactor')
|
logger.debug('%r is running after closing for %.1f seconds',
|
||||||
|
self, time.monotonic() - start_time)
|
||||||
|
next_msg = time.monotonic() + msg_update
|
||||||
|
|
||||||
|
# handle a few events, or timeout
|
||||||
|
self._poll(msg_update)
|
||||||
|
|
||||||
self._results = []
|
self._results = []
|
||||||
if self._iocp is not None:
|
|
||||||
_winapi.CloseHandle(self._iocp)
|
_winapi.CloseHandle(self._iocp)
|
||||||
self._iocp = None
|
self._iocp = None
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.close()
|
self.close()
|
||||||
@@ -773,8 +890,12 @@ class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
|
|||||||
SelectorEventLoop = _WindowsSelectorEventLoop
|
SelectorEventLoop = _WindowsSelectorEventLoop
|
||||||
|
|
||||||
|
|
||||||
class _WindowsDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
||||||
_loop_factory = SelectorEventLoop
|
_loop_factory = SelectorEventLoop
|
||||||
|
|
||||||
|
|
||||||
DefaultEventLoopPolicy = _WindowsDefaultEventLoopPolicy
|
class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
||||||
|
_loop_factory = ProactorEventLoop
|
||||||
|
|
||||||
|
|
||||||
|
DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy
|
||||||
|
|||||||
71
Lib/asyncio/windows_utils.py
vendored
71
Lib/asyncio/windows_utils.py
vendored
@@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Various Windows specific bits and pieces."""
|
||||||
Various Windows specific bits and pieces
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -11,13 +9,12 @@ import _winapi
|
|||||||
import itertools
|
import itertools
|
||||||
import msvcrt
|
import msvcrt
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
|
__all__ = 'pipe', 'Popen', 'PIPE', 'PipeHandle'
|
||||||
|
|
||||||
|
|
||||||
# Constants/globals
|
# Constants/globals
|
||||||
@@ -29,61 +26,14 @@ STDOUT = subprocess.STDOUT
|
|||||||
_mmap_counter = itertools.count()
|
_mmap_counter = itertools.count()
|
||||||
|
|
||||||
|
|
||||||
if hasattr(socket, 'socketpair'):
|
|
||||||
# Since Python 3.5, socket.socketpair() is now also available on Windows
|
|
||||||
socketpair = socket.socketpair
|
|
||||||
else:
|
|
||||||
# Replacement for socket.socketpair()
|
|
||||||
def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
|
|
||||||
"""A socket pair usable as a self-pipe, for Windows.
|
|
||||||
|
|
||||||
Origin: https://gist.github.com/4325783, by Geert Jansen.
|
|
||||||
Public domain.
|
|
||||||
"""
|
|
||||||
if family == socket.AF_INET:
|
|
||||||
host = '127.0.0.1'
|
|
||||||
elif family == socket.AF_INET6:
|
|
||||||
host = '::1'
|
|
||||||
else:
|
|
||||||
raise ValueError("Only AF_INET and AF_INET6 socket address "
|
|
||||||
"families are supported")
|
|
||||||
if type != socket.SOCK_STREAM:
|
|
||||||
raise ValueError("Only SOCK_STREAM socket type is supported")
|
|
||||||
if proto != 0:
|
|
||||||
raise ValueError("Only protocol zero is supported")
|
|
||||||
|
|
||||||
# We create a connected TCP socket. Note the trick with setblocking(0)
|
|
||||||
# that prevents us from having to create a thread.
|
|
||||||
lsock = socket.socket(family, type, proto)
|
|
||||||
try:
|
|
||||||
lsock.bind((host, 0))
|
|
||||||
lsock.listen(1)
|
|
||||||
# On IPv6, ignore flow_info and scope_id
|
|
||||||
addr, port = lsock.getsockname()[:2]
|
|
||||||
csock = socket.socket(family, type, proto)
|
|
||||||
try:
|
|
||||||
csock.setblocking(False)
|
|
||||||
try:
|
|
||||||
csock.connect((addr, port))
|
|
||||||
except (BlockingIOError, InterruptedError):
|
|
||||||
pass
|
|
||||||
csock.setblocking(True)
|
|
||||||
ssock, _ = lsock.accept()
|
|
||||||
except:
|
|
||||||
csock.close()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
lsock.close()
|
|
||||||
return (ssock, csock)
|
|
||||||
|
|
||||||
|
|
||||||
# Replacement for os.pipe() using handles instead of fds
|
# Replacement for os.pipe() using handles instead of fds
|
||||||
|
|
||||||
|
|
||||||
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
|
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
|
||||||
"""Like os.pipe() but with overlapped support and using handles not fds."""
|
"""Like os.pipe() but with overlapped support and using handles not fds."""
|
||||||
address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
|
address = tempfile.mktemp(
|
||||||
(os.getpid(), next(_mmap_counter)))
|
prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
|
||||||
|
os.getpid(), next(_mmap_counter)))
|
||||||
|
|
||||||
if duplex:
|
if duplex:
|
||||||
openmode = _winapi.PIPE_ACCESS_DUPLEX
|
openmode = _winapi.PIPE_ACCESS_DUPLEX
|
||||||
@@ -138,10 +88,10 @@ class PipeHandle:
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self._handle is not None:
|
if self._handle is not None:
|
||||||
handle = 'handle=%r' % self._handle
|
handle = f'handle={self._handle!r}'
|
||||||
else:
|
else:
|
||||||
handle = 'closed'
|
handle = 'closed'
|
||||||
return '<%s %s>' % (self.__class__.__name__, handle)
|
return f'<{self.__class__.__name__} {handle}>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def handle(self):
|
def handle(self):
|
||||||
@@ -149,7 +99,7 @@ class PipeHandle:
|
|||||||
|
|
||||||
def fileno(self):
|
def fileno(self):
|
||||||
if self._handle is None:
|
if self._handle is None:
|
||||||
raise ValueError("I/O operatioon on closed pipe")
|
raise ValueError("I/O operation on closed pipe")
|
||||||
return self._handle
|
return self._handle
|
||||||
|
|
||||||
def close(self, *, CloseHandle=_winapi.CloseHandle):
|
def close(self, *, CloseHandle=_winapi.CloseHandle):
|
||||||
@@ -157,10 +107,9 @@ class PipeHandle:
|
|||||||
CloseHandle(self._handle)
|
CloseHandle(self._handle)
|
||||||
self._handle = None
|
self._handle = None
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self, _warn=warnings.warn):
|
||||||
if self._handle is not None:
|
if self._handle is not None:
|
||||||
warnings.warn("unclosed %r" % self, ResourceWarning,
|
_warn(f"unclosed {self!r}", ResourceWarning, source=self)
|
||||||
source=self)
|
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
|||||||
161
Lib/base64.py
vendored
161
Lib/base64.py
vendored
@@ -1,4 +1,4 @@
|
|||||||
#! /usr/bin/python3.6
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
"""Base16, Base32, Base64 (RFC 3548), Base85 and Ascii85 data encodings"""
|
"""Base16, Base32, Base64 (RFC 3548), Base85 and Ascii85 data encodings"""
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ __all__ = [
|
|||||||
'encode', 'decode', 'encodebytes', 'decodebytes',
|
'encode', 'decode', 'encodebytes', 'decodebytes',
|
||||||
# Generalized interface for other encodings
|
# Generalized interface for other encodings
|
||||||
'b64encode', 'b64decode', 'b32encode', 'b32decode',
|
'b64encode', 'b64decode', 'b32encode', 'b32decode',
|
||||||
'b16encode', 'b16decode',
|
'b32hexencode', 'b32hexdecode', 'b16encode', 'b16decode',
|
||||||
# Base85 and Ascii85 encodings
|
# Base85 and Ascii85 encodings
|
||||||
'b85encode', 'b85decode', 'a85encode', 'a85decode',
|
'b85encode', 'b85decode', 'a85encode', 'a85decode',
|
||||||
# Standard Base64 encoding
|
# Standard Base64 encoding
|
||||||
@@ -76,15 +76,16 @@ def b64decode(s, altchars=None, validate=False):
|
|||||||
normal base-64 alphabet nor the alternative alphabet are discarded prior
|
normal base-64 alphabet nor the alternative alphabet are discarded prior
|
||||||
to the padding check. If validate is True, these non-alphabet characters
|
to the padding check. If validate is True, these non-alphabet characters
|
||||||
in the input result in a binascii.Error.
|
in the input result in a binascii.Error.
|
||||||
|
For more information about the strict base64 check, see:
|
||||||
|
|
||||||
|
https://docs.python.org/3.11/library/binascii.html#binascii.a2b_base64
|
||||||
"""
|
"""
|
||||||
s = _bytes_from_decode_data(s)
|
s = _bytes_from_decode_data(s)
|
||||||
if altchars is not None:
|
if altchars is not None:
|
||||||
altchars = _bytes_from_decode_data(altchars)
|
altchars = _bytes_from_decode_data(altchars)
|
||||||
assert len(altchars) == 2, repr(altchars)
|
assert len(altchars) == 2, repr(altchars)
|
||||||
s = s.translate(bytes.maketrans(altchars, b'+/'))
|
s = s.translate(bytes.maketrans(altchars, b'+/'))
|
||||||
if validate and not re.match(b'^[A-Za-z0-9+/]*={0,2}$', s):
|
return binascii.a2b_base64(s, strict_mode=validate)
|
||||||
raise binascii.Error('Non-base64 digit found')
|
|
||||||
return binascii.a2b_base64(s)
|
|
||||||
|
|
||||||
|
|
||||||
def standard_b64encode(s):
|
def standard_b64encode(s):
|
||||||
@@ -135,19 +136,40 @@ def urlsafe_b64decode(s):
|
|||||||
|
|
||||||
|
|
||||||
# Base32 encoding/decoding must be done in Python
|
# Base32 encoding/decoding must be done in Python
|
||||||
_b32alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
_B32_ENCODE_DOCSTRING = '''
|
||||||
_b32tab2 = None
|
Encode the bytes-like objects using {encoding} and return a bytes object.
|
||||||
_b32rev = None
|
'''
|
||||||
|
_B32_DECODE_DOCSTRING = '''
|
||||||
|
Decode the {encoding} encoded bytes-like object or ASCII string s.
|
||||||
|
|
||||||
def b32encode(s):
|
Optional casefold is a flag specifying whether a lowercase alphabet is
|
||||||
"""Encode the bytes-like object s using Base32 and return a bytes object.
|
acceptable as input. For security purposes, the default is False.
|
||||||
"""
|
{extra_args}
|
||||||
|
The result is returned as a bytes object. A binascii.Error is raised if
|
||||||
|
the input is incorrectly padded or if there are non-alphabet
|
||||||
|
characters present in the input.
|
||||||
|
'''
|
||||||
|
_B32_DECODE_MAP01_DOCSTRING = '''
|
||||||
|
RFC 3548 allows for optional mapping of the digit 0 (zero) to the
|
||||||
|
letter O (oh), and for optional mapping of the digit 1 (one) to
|
||||||
|
either the letter I (eye) or letter L (el). The optional argument
|
||||||
|
map01 when not None, specifies which letter the digit 1 should be
|
||||||
|
mapped to (when map01 is not None, the digit 0 is always mapped to
|
||||||
|
the letter O). For security purposes the default is None, so that
|
||||||
|
0 and 1 are not allowed in the input.
|
||||||
|
'''
|
||||||
|
_b32alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||||
|
_b32hexalphabet = b'0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||||
|
_b32tab2 = {}
|
||||||
|
_b32rev = {}
|
||||||
|
|
||||||
|
def _b32encode(alphabet, s):
|
||||||
global _b32tab2
|
global _b32tab2
|
||||||
# Delay the initialization of the table to not waste memory
|
# Delay the initialization of the table to not waste memory
|
||||||
# if the function is never called
|
# if the function is never called
|
||||||
if _b32tab2 is None:
|
if alphabet not in _b32tab2:
|
||||||
b32tab = [bytes((i,)) for i in _b32alphabet]
|
b32tab = [bytes((i,)) for i in alphabet]
|
||||||
_b32tab2 = [a + b for a in b32tab for b in b32tab]
|
_b32tab2[alphabet] = [a + b for a in b32tab for b in b32tab]
|
||||||
b32tab = None
|
b32tab = None
|
||||||
|
|
||||||
if not isinstance(s, bytes_types):
|
if not isinstance(s, bytes_types):
|
||||||
@@ -158,9 +180,9 @@ def b32encode(s):
|
|||||||
s = s + b'\0' * (5 - leftover) # Don't use += !
|
s = s + b'\0' * (5 - leftover) # Don't use += !
|
||||||
encoded = bytearray()
|
encoded = bytearray()
|
||||||
from_bytes = int.from_bytes
|
from_bytes = int.from_bytes
|
||||||
b32tab2 = _b32tab2
|
b32tab2 = _b32tab2[alphabet]
|
||||||
for i in range(0, len(s), 5):
|
for i in range(0, len(s), 5):
|
||||||
c = from_bytes(s[i: i + 5], 'big')
|
c = from_bytes(s[i: i + 5]) # big endian
|
||||||
encoded += (b32tab2[c >> 30] + # bits 1 - 10
|
encoded += (b32tab2[c >> 30] + # bits 1 - 10
|
||||||
b32tab2[(c >> 20) & 0x3ff] + # bits 11 - 20
|
b32tab2[(c >> 20) & 0x3ff] + # bits 11 - 20
|
||||||
b32tab2[(c >> 10) & 0x3ff] + # bits 21 - 30
|
b32tab2[(c >> 10) & 0x3ff] + # bits 21 - 30
|
||||||
@@ -177,29 +199,12 @@ def b32encode(s):
|
|||||||
encoded[-1:] = b'='
|
encoded[-1:] = b'='
|
||||||
return bytes(encoded)
|
return bytes(encoded)
|
||||||
|
|
||||||
def b32decode(s, casefold=False, map01=None):
|
def _b32decode(alphabet, s, casefold=False, map01=None):
|
||||||
"""Decode the Base32 encoded bytes-like object or ASCII string s.
|
|
||||||
|
|
||||||
Optional casefold is a flag specifying whether a lowercase alphabet is
|
|
||||||
acceptable as input. For security purposes, the default is False.
|
|
||||||
|
|
||||||
RFC 3548 allows for optional mapping of the digit 0 (zero) to the
|
|
||||||
letter O (oh), and for optional mapping of the digit 1 (one) to
|
|
||||||
either the letter I (eye) or letter L (el). The optional argument
|
|
||||||
map01 when not None, specifies which letter the digit 1 should be
|
|
||||||
mapped to (when map01 is not None, the digit 0 is always mapped to
|
|
||||||
the letter O). For security purposes the default is None, so that
|
|
||||||
0 and 1 are not allowed in the input.
|
|
||||||
|
|
||||||
The result is returned as a bytes object. A binascii.Error is raised if
|
|
||||||
the input is incorrectly padded or if there are non-alphabet
|
|
||||||
characters present in the input.
|
|
||||||
"""
|
|
||||||
global _b32rev
|
global _b32rev
|
||||||
# Delay the initialization of the table to not waste memory
|
# Delay the initialization of the table to not waste memory
|
||||||
# if the function is never called
|
# if the function is never called
|
||||||
if _b32rev is None:
|
if alphabet not in _b32rev:
|
||||||
_b32rev = {v: k for k, v in enumerate(_b32alphabet)}
|
_b32rev[alphabet] = {v: k for k, v in enumerate(alphabet)}
|
||||||
s = _bytes_from_decode_data(s)
|
s = _bytes_from_decode_data(s)
|
||||||
if len(s) % 8:
|
if len(s) % 8:
|
||||||
raise binascii.Error('Incorrect padding')
|
raise binascii.Error('Incorrect padding')
|
||||||
@@ -220,7 +225,7 @@ def b32decode(s, casefold=False, map01=None):
|
|||||||
padchars = l - len(s)
|
padchars = l - len(s)
|
||||||
# Now decode the full quanta
|
# Now decode the full quanta
|
||||||
decoded = bytearray()
|
decoded = bytearray()
|
||||||
b32rev = _b32rev
|
b32rev = _b32rev[alphabet]
|
||||||
for i in range(0, len(s), 8):
|
for i in range(0, len(s), 8):
|
||||||
quanta = s[i: i + 8]
|
quanta = s[i: i + 8]
|
||||||
acc = 0
|
acc = 0
|
||||||
@@ -229,18 +234,38 @@ def b32decode(s, casefold=False, map01=None):
|
|||||||
acc = (acc << 5) + b32rev[c]
|
acc = (acc << 5) + b32rev[c]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise binascii.Error('Non-base32 digit found') from None
|
raise binascii.Error('Non-base32 digit found') from None
|
||||||
decoded += acc.to_bytes(5, 'big')
|
decoded += acc.to_bytes(5) # big endian
|
||||||
# Process the last, partial quanta
|
# Process the last, partial quanta
|
||||||
if l % 8 or padchars not in {0, 1, 3, 4, 6}:
|
if l % 8 or padchars not in {0, 1, 3, 4, 6}:
|
||||||
raise binascii.Error('Incorrect padding')
|
raise binascii.Error('Incorrect padding')
|
||||||
if padchars and decoded:
|
if padchars and decoded:
|
||||||
acc <<= 5 * padchars
|
acc <<= 5 * padchars
|
||||||
last = acc.to_bytes(5, 'big')
|
last = acc.to_bytes(5) # big endian
|
||||||
leftover = (43 - 5 * padchars) // 8 # 1: 4, 3: 3, 4: 2, 6: 1
|
leftover = (43 - 5 * padchars) // 8 # 1: 4, 3: 3, 4: 2, 6: 1
|
||||||
decoded[-5:] = last[:leftover]
|
decoded[-5:] = last[:leftover]
|
||||||
return bytes(decoded)
|
return bytes(decoded)
|
||||||
|
|
||||||
|
|
||||||
|
def b32encode(s):
|
||||||
|
return _b32encode(_b32alphabet, s)
|
||||||
|
b32encode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32')
|
||||||
|
|
||||||
|
def b32decode(s, casefold=False, map01=None):
|
||||||
|
return _b32decode(_b32alphabet, s, casefold, map01)
|
||||||
|
b32decode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32',
|
||||||
|
extra_args=_B32_DECODE_MAP01_DOCSTRING)
|
||||||
|
|
||||||
|
def b32hexencode(s):
|
||||||
|
return _b32encode(_b32hexalphabet, s)
|
||||||
|
b32hexencode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32hex')
|
||||||
|
|
||||||
|
def b32hexdecode(s, casefold=False):
|
||||||
|
# base32hex does not have the 01 mapping
|
||||||
|
return _b32decode(_b32hexalphabet, s, casefold)
|
||||||
|
b32hexdecode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32hex',
|
||||||
|
extra_args='')
|
||||||
|
|
||||||
|
|
||||||
# RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns
|
# RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns
|
||||||
# lowercase. The RFC also recommends against accepting input case
|
# lowercase. The RFC also recommends against accepting input case
|
||||||
# insensitively.
|
# insensitively.
|
||||||
@@ -320,7 +345,7 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
|
|||||||
global _a85chars, _a85chars2
|
global _a85chars, _a85chars2
|
||||||
# Delay the initialization of tables to not waste memory
|
# Delay the initialization of tables to not waste memory
|
||||||
# if the function is never called
|
# if the function is never called
|
||||||
if _a85chars is None:
|
if _a85chars2 is None:
|
||||||
_a85chars = [bytes((i,)) for i in range(33, 118)]
|
_a85chars = [bytes((i,)) for i in range(33, 118)]
|
||||||
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
|
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
|
||||||
|
|
||||||
@@ -428,7 +453,7 @@ def b85encode(b, pad=False):
|
|||||||
global _b85chars, _b85chars2
|
global _b85chars, _b85chars2
|
||||||
# Delay the initialization of tables to not waste memory
|
# Delay the initialization of tables to not waste memory
|
||||||
# if the function is never called
|
# if the function is never called
|
||||||
if _b85chars is None:
|
if _b85chars2 is None:
|
||||||
_b85chars = [bytes((i,)) for i in _b85alphabet]
|
_b85chars = [bytes((i,)) for i in _b85alphabet]
|
||||||
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
|
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
|
||||||
return _85encode(b, _b85chars, _b85chars2, pad)
|
return _85encode(b, _b85chars, _b85chars2, pad)
|
||||||
@@ -483,14 +508,8 @@ MAXBINSIZE = (MAXLINESIZE//4)*3
|
|||||||
|
|
||||||
def encode(input, output):
|
def encode(input, output):
|
||||||
"""Encode a file; input and output are binary files."""
|
"""Encode a file; input and output are binary files."""
|
||||||
while True:
|
while s := input.read(MAXBINSIZE):
|
||||||
s = input.read(MAXBINSIZE)
|
while len(s) < MAXBINSIZE and (ns := input.read(MAXBINSIZE-len(s))):
|
||||||
if not s:
|
|
||||||
break
|
|
||||||
while len(s) < MAXBINSIZE:
|
|
||||||
ns = input.read(MAXBINSIZE-len(s))
|
|
||||||
if not ns:
|
|
||||||
break
|
|
||||||
s += ns
|
s += ns
|
||||||
line = binascii.b2a_base64(s)
|
line = binascii.b2a_base64(s)
|
||||||
output.write(line)
|
output.write(line)
|
||||||
@@ -498,10 +517,7 @@ def encode(input, output):
|
|||||||
|
|
||||||
def decode(input, output):
|
def decode(input, output):
|
||||||
"""Decode a file; input and output are binary files."""
|
"""Decode a file; input and output are binary files."""
|
||||||
while True:
|
while line := input.readline():
|
||||||
line = input.readline()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
s = binascii.a2b_base64(line)
|
s = binascii.a2b_base64(line)
|
||||||
output.write(s)
|
output.write(s)
|
||||||
|
|
||||||
@@ -531,49 +547,34 @@ def encodebytes(s):
|
|||||||
pieces.append(binascii.b2a_base64(chunk))
|
pieces.append(binascii.b2a_base64(chunk))
|
||||||
return b"".join(pieces)
|
return b"".join(pieces)
|
||||||
|
|
||||||
def encodestring(s):
|
|
||||||
"""Legacy alias of encodebytes()."""
|
|
||||||
import warnings
|
|
||||||
warnings.warn("encodestring() is a deprecated alias since 3.1, "
|
|
||||||
"use encodebytes()",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
return encodebytes(s)
|
|
||||||
|
|
||||||
|
|
||||||
def decodebytes(s):
|
def decodebytes(s):
|
||||||
"""Decode a bytestring of base-64 data into a bytes object."""
|
"""Decode a bytestring of base-64 data into a bytes object."""
|
||||||
_input_type_check(s)
|
_input_type_check(s)
|
||||||
return binascii.a2b_base64(s)
|
return binascii.a2b_base64(s)
|
||||||
|
|
||||||
def decodestring(s):
|
|
||||||
"""Legacy alias of decodebytes()."""
|
|
||||||
import warnings
|
|
||||||
warnings.warn("decodestring() is a deprecated alias since Python 3.1, "
|
|
||||||
"use decodebytes()",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
return decodebytes(s)
|
|
||||||
|
|
||||||
|
|
||||||
# Usable as a script...
|
# Usable as a script...
|
||||||
def main():
|
def main():
|
||||||
"""Small main program"""
|
"""Small main program"""
|
||||||
import sys, getopt
|
import sys, getopt
|
||||||
|
usage = f"""usage: {sys.argv[0]} [-h|-d|-e|-u] [file|-]
|
||||||
|
-h: print this help message and exit
|
||||||
|
-d, -u: decode
|
||||||
|
-e: encode (default)"""
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'deut')
|
opts, args = getopt.getopt(sys.argv[1:], 'hdeu')
|
||||||
except getopt.error as msg:
|
except getopt.error as msg:
|
||||||
sys.stdout = sys.stderr
|
sys.stdout = sys.stderr
|
||||||
print(msg)
|
print(msg)
|
||||||
print("""usage: %s [-d|-e|-u|-t] [file|-]
|
print(usage)
|
||||||
-d, -u: decode
|
|
||||||
-e: encode (default)
|
|
||||||
-t: encode and decode string 'Aladdin:open sesame'"""%sys.argv[0])
|
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
func = encode
|
func = encode
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == '-e': func = encode
|
if o == '-e': func = encode
|
||||||
if o == '-d': func = decode
|
if o == '-d': func = decode
|
||||||
if o == '-u': func = decode
|
if o == '-u': func = decode
|
||||||
if o == '-t': test(); return
|
if o == '-h': print(usage); return
|
||||||
if args and args[0] != '-':
|
if args and args[0] != '-':
|
||||||
with open(args[0], 'rb') as f:
|
with open(args[0], 'rb') as f:
|
||||||
func(f, sys.stdout.buffer)
|
func(f, sys.stdout.buffer)
|
||||||
@@ -581,15 +582,5 @@ def main():
|
|||||||
func(sys.stdin.buffer, sys.stdout.buffer)
|
func(sys.stdin.buffer, sys.stdout.buffer)
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
s0 = b"Aladdin:open sesame"
|
|
||||||
print(repr(s0))
|
|
||||||
s1 = encodebytes(s0)
|
|
||||||
print(repr(s1))
|
|
||||||
s2 = decodebytes(s1)
|
|
||||||
print(repr(s2))
|
|
||||||
assert s0 == s2
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
26
Lib/bdb.py
vendored
26
Lib/bdb.py
vendored
@@ -570,9 +570,12 @@ class Bdb:
|
|||||||
rv = frame.f_locals['__return__']
|
rv = frame.f_locals['__return__']
|
||||||
s += '->'
|
s += '->'
|
||||||
s += reprlib.repr(rv)
|
s += reprlib.repr(rv)
|
||||||
line = linecache.getline(filename, lineno, frame.f_globals)
|
if lineno is not None:
|
||||||
if line:
|
line = linecache.getline(filename, lineno, frame.f_globals)
|
||||||
s += lprefix + line.strip()
|
if line:
|
||||||
|
s += lprefix + line.strip()
|
||||||
|
else:
|
||||||
|
s += f'{lprefix}Warning: lineno is None'
|
||||||
return s
|
return s
|
||||||
|
|
||||||
# The following methods can be called by clients to use
|
# The following methods can be called by clients to use
|
||||||
@@ -805,15 +808,18 @@ def checkfuncname(b, frame):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# Determines if there is an effective (active) breakpoint at this
|
|
||||||
# line of code. Returns breakpoint number or 0 if none
|
|
||||||
def effective(file, line, frame):
|
def effective(file, line, frame):
|
||||||
"""Determine which breakpoint for this file:line is to be acted upon.
|
"""Return (active breakpoint, delete temporary flag) or (None, None) as
|
||||||
|
breakpoint to act upon.
|
||||||
|
|
||||||
Called only if we know there is a breakpoint at this location. Return
|
The "active breakpoint" is the first entry in bplist[line, file] (which
|
||||||
the breakpoint that was triggered and a boolean that indicates if it is
|
must exist) that is enabled, for which checkfuncname is True, and that
|
||||||
ok to delete a temporary breakpoint. Return (None, None) if there is no
|
has neither a False condition nor a positive ignore count. The flag,
|
||||||
matching breakpoint.
|
meaning that a temporary breakpoint should be deleted, is False only
|
||||||
|
when the condiion cannot be evaluated (in which case, ignore count is
|
||||||
|
ignored).
|
||||||
|
|
||||||
|
If no such entry exists, then (None, None) is returned.
|
||||||
"""
|
"""
|
||||||
possibles = Breakpoint.bplist[file, line]
|
possibles = Breakpoint.bplist[file, line]
|
||||||
for b in possibles:
|
for b in possibles:
|
||||||
|
|||||||
502
Lib/binhex.py
vendored
502
Lib/binhex.py
vendored
@@ -1,502 +0,0 @@
|
|||||||
"""Macintosh binhex compression/decompression.
|
|
||||||
|
|
||||||
easy interface:
|
|
||||||
binhex(inputfilename, outputfilename)
|
|
||||||
hexbin(inputfilename, outputfilename)
|
|
||||||
"""
|
|
||||||
|
|
||||||
#
|
|
||||||
# Jack Jansen, CWI, August 1995.
|
|
||||||
#
|
|
||||||
# The module is supposed to be as compatible as possible. Especially the
|
|
||||||
# easy interface should work "as expected" on any platform.
|
|
||||||
# XXXX Note: currently, textfiles appear in mac-form on all platforms.
|
|
||||||
# We seem to lack a simple character-translate in python.
|
|
||||||
# (we should probably use ISO-Latin-1 on all but the mac platform).
|
|
||||||
# XXXX The simple routines are too simple: they expect to hold the complete
|
|
||||||
# files in-core. Should be fixed.
|
|
||||||
# XXXX It would be nice to handle AppleDouble format on unix
|
|
||||||
# (for servers serving macs).
|
|
||||||
# XXXX I don't understand what happens when you get 0x90 times the same byte on
|
|
||||||
# input. The resulting code (xx 90 90) would appear to be interpreted as an
|
|
||||||
# escaped *value* of 0x90. All coders I've seen appear to ignore this nicety...
|
|
||||||
#
|
|
||||||
import binascii
|
|
||||||
import contextlib
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn('the binhex module is deprecated', DeprecationWarning,
|
|
||||||
stacklevel=2)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["binhex","hexbin","Error"]
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# States (what have we written)
|
|
||||||
_DID_HEADER = 0
|
|
||||||
_DID_DATA = 1
|
|
||||||
|
|
||||||
# Various constants
|
|
||||||
REASONABLY_LARGE = 32768 # Minimal amount we pass the rle-coder
|
|
||||||
LINELEN = 64
|
|
||||||
RUNCHAR = b"\x90"
|
|
||||||
|
|
||||||
#
|
|
||||||
# This code is no longer byte-order dependent
|
|
||||||
|
|
||||||
|
|
||||||
class FInfo:
|
|
||||||
def __init__(self):
|
|
||||||
self.Type = '????'
|
|
||||||
self.Creator = '????'
|
|
||||||
self.Flags = 0
|
|
||||||
|
|
||||||
def getfileinfo(name):
|
|
||||||
finfo = FInfo()
|
|
||||||
with io.open(name, 'rb') as fp:
|
|
||||||
# Quick check for textfile
|
|
||||||
data = fp.read(512)
|
|
||||||
if 0 not in data:
|
|
||||||
finfo.Type = 'TEXT'
|
|
||||||
fp.seek(0, 2)
|
|
||||||
dsize = fp.tell()
|
|
||||||
dir, file = os.path.split(name)
|
|
||||||
file = file.replace(':', '-', 1)
|
|
||||||
return file, finfo, dsize, 0
|
|
||||||
|
|
||||||
class openrsrc:
|
|
||||||
def __init__(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def read(self, *args):
|
|
||||||
return b''
|
|
||||||
|
|
||||||
def write(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# DeprecationWarning is already emitted on "import binhex". There is no need
|
|
||||||
# to repeat the warning at each call to deprecated binascii functions.
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _ignore_deprecation_warning():
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
warnings.filterwarnings('ignore', '', DeprecationWarning)
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
class _Hqxcoderengine:
|
|
||||||
"""Write data to the coder in 3-byte chunks"""
|
|
||||||
|
|
||||||
def __init__(self, ofp):
|
|
||||||
self.ofp = ofp
|
|
||||||
self.data = b''
|
|
||||||
self.hqxdata = b''
|
|
||||||
self.linelen = LINELEN - 1
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
self.data = self.data + data
|
|
||||||
datalen = len(self.data)
|
|
||||||
todo = (datalen // 3) * 3
|
|
||||||
data = self.data[:todo]
|
|
||||||
self.data = self.data[todo:]
|
|
||||||
if not data:
|
|
||||||
return
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)
|
|
||||||
self._flush(0)
|
|
||||||
|
|
||||||
def _flush(self, force):
|
|
||||||
first = 0
|
|
||||||
while first <= len(self.hqxdata) - self.linelen:
|
|
||||||
last = first + self.linelen
|
|
||||||
self.ofp.write(self.hqxdata[first:last] + b'\r')
|
|
||||||
self.linelen = LINELEN
|
|
||||||
first = last
|
|
||||||
self.hqxdata = self.hqxdata[first:]
|
|
||||||
if force:
|
|
||||||
self.ofp.write(self.hqxdata + b':\r')
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.data:
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
self.hqxdata = self.hqxdata + binascii.b2a_hqx(self.data)
|
|
||||||
self._flush(1)
|
|
||||||
self.ofp.close()
|
|
||||||
del self.ofp
|
|
||||||
|
|
||||||
class _Rlecoderengine:
|
|
||||||
"""Write data to the RLE-coder in suitably large chunks"""
|
|
||||||
|
|
||||||
def __init__(self, ofp):
|
|
||||||
self.ofp = ofp
|
|
||||||
self.data = b''
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
self.data = self.data + data
|
|
||||||
if len(self.data) < REASONABLY_LARGE:
|
|
||||||
return
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
rledata = binascii.rlecode_hqx(self.data)
|
|
||||||
self.ofp.write(rledata)
|
|
||||||
self.data = b''
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.data:
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
rledata = binascii.rlecode_hqx(self.data)
|
|
||||||
self.ofp.write(rledata)
|
|
||||||
self.ofp.close()
|
|
||||||
del self.ofp
|
|
||||||
|
|
||||||
class BinHex:
|
|
||||||
def __init__(self, name_finfo_dlen_rlen, ofp):
|
|
||||||
name, finfo, dlen, rlen = name_finfo_dlen_rlen
|
|
||||||
close_on_error = False
|
|
||||||
if isinstance(ofp, str):
|
|
||||||
ofname = ofp
|
|
||||||
ofp = io.open(ofname, 'wb')
|
|
||||||
close_on_error = True
|
|
||||||
try:
|
|
||||||
ofp.write(b'(This file must be converted with BinHex 4.0)\r\r:')
|
|
||||||
hqxer = _Hqxcoderengine(ofp)
|
|
||||||
self.ofp = _Rlecoderengine(hqxer)
|
|
||||||
self.crc = 0
|
|
||||||
if finfo is None:
|
|
||||||
finfo = FInfo()
|
|
||||||
self.dlen = dlen
|
|
||||||
self.rlen = rlen
|
|
||||||
self._writeinfo(name, finfo)
|
|
||||||
self.state = _DID_HEADER
|
|
||||||
except:
|
|
||||||
if close_on_error:
|
|
||||||
ofp.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _writeinfo(self, name, finfo):
|
|
||||||
nl = len(name)
|
|
||||||
if nl > 63:
|
|
||||||
raise Error('Filename too long')
|
|
||||||
d = bytes([nl]) + name.encode("latin-1") + b'\0'
|
|
||||||
tp, cr = finfo.Type, finfo.Creator
|
|
||||||
if isinstance(tp, str):
|
|
||||||
tp = tp.encode("latin-1")
|
|
||||||
if isinstance(cr, str):
|
|
||||||
cr = cr.encode("latin-1")
|
|
||||||
d2 = tp + cr
|
|
||||||
|
|
||||||
# Force all structs to be packed with big-endian
|
|
||||||
d3 = struct.pack('>h', finfo.Flags)
|
|
||||||
d4 = struct.pack('>ii', self.dlen, self.rlen)
|
|
||||||
info = d + d2 + d3 + d4
|
|
||||||
self._write(info)
|
|
||||||
self._writecrc()
|
|
||||||
|
|
||||||
def _write(self, data):
|
|
||||||
self.crc = binascii.crc_hqx(data, self.crc)
|
|
||||||
self.ofp.write(data)
|
|
||||||
|
|
||||||
def _writecrc(self):
|
|
||||||
# XXXX Should this be here??
|
|
||||||
# self.crc = binascii.crc_hqx('\0\0', self.crc)
|
|
||||||
if self.crc < 0:
|
|
||||||
fmt = '>h'
|
|
||||||
else:
|
|
||||||
fmt = '>H'
|
|
||||||
self.ofp.write(struct.pack(fmt, self.crc))
|
|
||||||
self.crc = 0
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
if self.state != _DID_HEADER:
|
|
||||||
raise Error('Writing data at the wrong time')
|
|
||||||
self.dlen = self.dlen - len(data)
|
|
||||||
self._write(data)
|
|
||||||
|
|
||||||
def close_data(self):
|
|
||||||
if self.dlen != 0:
|
|
||||||
raise Error('Incorrect data size, diff=%r' % (self.rlen,))
|
|
||||||
self._writecrc()
|
|
||||||
self.state = _DID_DATA
|
|
||||||
|
|
||||||
def write_rsrc(self, data):
|
|
||||||
if self.state < _DID_DATA:
|
|
||||||
self.close_data()
|
|
||||||
if self.state != _DID_DATA:
|
|
||||||
raise Error('Writing resource data at the wrong time')
|
|
||||||
self.rlen = self.rlen - len(data)
|
|
||||||
self._write(data)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.state is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if self.state < _DID_DATA:
|
|
||||||
self.close_data()
|
|
||||||
if self.state != _DID_DATA:
|
|
||||||
raise Error('Close at the wrong time')
|
|
||||||
if self.rlen != 0:
|
|
||||||
raise Error("Incorrect resource-datasize, diff=%r" % (self.rlen,))
|
|
||||||
self._writecrc()
|
|
||||||
finally:
|
|
||||||
self.state = None
|
|
||||||
ofp = self.ofp
|
|
||||||
del self.ofp
|
|
||||||
ofp.close()
|
|
||||||
|
|
||||||
def binhex(inp, out):
|
|
||||||
"""binhex(infilename, outfilename): create binhex-encoded copy of a file"""
|
|
||||||
finfo = getfileinfo(inp)
|
|
||||||
ofp = BinHex(finfo, out)
|
|
||||||
|
|
||||||
with io.open(inp, 'rb') as ifp:
|
|
||||||
# XXXX Do textfile translation on non-mac systems
|
|
||||||
while True:
|
|
||||||
d = ifp.read(128000)
|
|
||||||
if not d: break
|
|
||||||
ofp.write(d)
|
|
||||||
ofp.close_data()
|
|
||||||
|
|
||||||
ifp = openrsrc(inp, 'rb')
|
|
||||||
while True:
|
|
||||||
d = ifp.read(128000)
|
|
||||||
if not d: break
|
|
||||||
ofp.write_rsrc(d)
|
|
||||||
ofp.close()
|
|
||||||
ifp.close()
|
|
||||||
|
|
||||||
class _Hqxdecoderengine:
|
|
||||||
"""Read data via the decoder in 4-byte chunks"""
|
|
||||||
|
|
||||||
def __init__(self, ifp):
|
|
||||||
self.ifp = ifp
|
|
||||||
self.eof = 0
|
|
||||||
|
|
||||||
def read(self, totalwtd):
|
|
||||||
"""Read at least wtd bytes (or until EOF)"""
|
|
||||||
decdata = b''
|
|
||||||
wtd = totalwtd
|
|
||||||
#
|
|
||||||
# The loop here is convoluted, since we don't really now how
|
|
||||||
# much to decode: there may be newlines in the incoming data.
|
|
||||||
while wtd > 0:
|
|
||||||
if self.eof: return decdata
|
|
||||||
wtd = ((wtd + 2) // 3) * 4
|
|
||||||
data = self.ifp.read(wtd)
|
|
||||||
#
|
|
||||||
# Next problem: there may not be a complete number of
|
|
||||||
# bytes in what we pass to a2b. Solve by yet another
|
|
||||||
# loop.
|
|
||||||
#
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
decdatacur, self.eof = binascii.a2b_hqx(data)
|
|
||||||
break
|
|
||||||
except binascii.Incomplete:
|
|
||||||
pass
|
|
||||||
newdata = self.ifp.read(1)
|
|
||||||
if not newdata:
|
|
||||||
raise Error('Premature EOF on binhex file')
|
|
||||||
data = data + newdata
|
|
||||||
decdata = decdata + decdatacur
|
|
||||||
wtd = totalwtd - len(decdata)
|
|
||||||
if not decdata and not self.eof:
|
|
||||||
raise Error('Premature EOF on binhex file')
|
|
||||||
return decdata
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.ifp.close()
|
|
||||||
|
|
||||||
class _Rledecoderengine:
|
|
||||||
"""Read data via the RLE-coder"""
|
|
||||||
|
|
||||||
def __init__(self, ifp):
|
|
||||||
self.ifp = ifp
|
|
||||||
self.pre_buffer = b''
|
|
||||||
self.post_buffer = b''
|
|
||||||
self.eof = 0
|
|
||||||
|
|
||||||
def read(self, wtd):
|
|
||||||
if wtd > len(self.post_buffer):
|
|
||||||
self._fill(wtd - len(self.post_buffer))
|
|
||||||
rv = self.post_buffer[:wtd]
|
|
||||||
self.post_buffer = self.post_buffer[wtd:]
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def _fill(self, wtd):
|
|
||||||
self.pre_buffer = self.pre_buffer + self.ifp.read(wtd + 4)
|
|
||||||
if self.ifp.eof:
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
self.post_buffer = self.post_buffer + \
|
|
||||||
binascii.rledecode_hqx(self.pre_buffer)
|
|
||||||
self.pre_buffer = b''
|
|
||||||
return
|
|
||||||
|
|
||||||
#
|
|
||||||
# Obfuscated code ahead. We have to take care that we don't
|
|
||||||
# end up with an orphaned RUNCHAR later on. So, we keep a couple
|
|
||||||
# of bytes in the buffer, depending on what the end of
|
|
||||||
# the buffer looks like:
|
|
||||||
# '\220\0\220' - Keep 3 bytes: repeated \220 (escaped as \220\0)
|
|
||||||
# '?\220' - Keep 2 bytes: repeated something-else
|
|
||||||
# '\220\0' - Escaped \220: Keep 2 bytes.
|
|
||||||
# '?\220?' - Complete repeat sequence: decode all
|
|
||||||
# otherwise: keep 1 byte.
|
|
||||||
#
|
|
||||||
mark = len(self.pre_buffer)
|
|
||||||
if self.pre_buffer[-3:] == RUNCHAR + b'\0' + RUNCHAR:
|
|
||||||
mark = mark - 3
|
|
||||||
elif self.pre_buffer[-1:] == RUNCHAR:
|
|
||||||
mark = mark - 2
|
|
||||||
elif self.pre_buffer[-2:] == RUNCHAR + b'\0':
|
|
||||||
mark = mark - 2
|
|
||||||
elif self.pre_buffer[-2:-1] == RUNCHAR:
|
|
||||||
pass # Decode all
|
|
||||||
else:
|
|
||||||
mark = mark - 1
|
|
||||||
|
|
||||||
with _ignore_deprecation_warning():
|
|
||||||
self.post_buffer = self.post_buffer + \
|
|
||||||
binascii.rledecode_hqx(self.pre_buffer[:mark])
|
|
||||||
self.pre_buffer = self.pre_buffer[mark:]
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.ifp.close()
|
|
||||||
|
|
||||||
class HexBin:
|
|
||||||
def __init__(self, ifp):
|
|
||||||
if isinstance(ifp, str):
|
|
||||||
ifp = io.open(ifp, 'rb')
|
|
||||||
#
|
|
||||||
# Find initial colon.
|
|
||||||
#
|
|
||||||
while True:
|
|
||||||
ch = ifp.read(1)
|
|
||||||
if not ch:
|
|
||||||
raise Error("No binhex data found")
|
|
||||||
# Cater for \r\n terminated lines (which show up as \n\r, hence
|
|
||||||
# all lines start with \r)
|
|
||||||
if ch == b'\r':
|
|
||||||
continue
|
|
||||||
if ch == b':':
|
|
||||||
break
|
|
||||||
|
|
||||||
hqxifp = _Hqxdecoderengine(ifp)
|
|
||||||
self.ifp = _Rledecoderengine(hqxifp)
|
|
||||||
self.crc = 0
|
|
||||||
self._readheader()
|
|
||||||
|
|
||||||
def _read(self, len):
|
|
||||||
data = self.ifp.read(len)
|
|
||||||
self.crc = binascii.crc_hqx(data, self.crc)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _checkcrc(self):
|
|
||||||
filecrc = struct.unpack('>h', self.ifp.read(2))[0] & 0xffff
|
|
||||||
#self.crc = binascii.crc_hqx('\0\0', self.crc)
|
|
||||||
# XXXX Is this needed??
|
|
||||||
self.crc = self.crc & 0xffff
|
|
||||||
if filecrc != self.crc:
|
|
||||||
raise Error('CRC error, computed %x, read %x'
|
|
||||||
% (self.crc, filecrc))
|
|
||||||
self.crc = 0
|
|
||||||
|
|
||||||
def _readheader(self):
|
|
||||||
len = self._read(1)
|
|
||||||
fname = self._read(ord(len))
|
|
||||||
rest = self._read(1 + 4 + 4 + 2 + 4 + 4)
|
|
||||||
self._checkcrc()
|
|
||||||
|
|
||||||
type = rest[1:5]
|
|
||||||
creator = rest[5:9]
|
|
||||||
flags = struct.unpack('>h', rest[9:11])[0]
|
|
||||||
self.dlen = struct.unpack('>l', rest[11:15])[0]
|
|
||||||
self.rlen = struct.unpack('>l', rest[15:19])[0]
|
|
||||||
|
|
||||||
self.FName = fname
|
|
||||||
self.FInfo = FInfo()
|
|
||||||
self.FInfo.Creator = creator
|
|
||||||
self.FInfo.Type = type
|
|
||||||
self.FInfo.Flags = flags
|
|
||||||
|
|
||||||
self.state = _DID_HEADER
|
|
||||||
|
|
||||||
def read(self, *n):
|
|
||||||
if self.state != _DID_HEADER:
|
|
||||||
raise Error('Read data at wrong time')
|
|
||||||
if n:
|
|
||||||
n = n[0]
|
|
||||||
n = min(n, self.dlen)
|
|
||||||
else:
|
|
||||||
n = self.dlen
|
|
||||||
rv = b''
|
|
||||||
while len(rv) < n:
|
|
||||||
rv = rv + self._read(n-len(rv))
|
|
||||||
self.dlen = self.dlen - n
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def close_data(self):
|
|
||||||
if self.state != _DID_HEADER:
|
|
||||||
raise Error('close_data at wrong time')
|
|
||||||
if self.dlen:
|
|
||||||
dummy = self._read(self.dlen)
|
|
||||||
self._checkcrc()
|
|
||||||
self.state = _DID_DATA
|
|
||||||
|
|
||||||
def read_rsrc(self, *n):
|
|
||||||
if self.state == _DID_HEADER:
|
|
||||||
self.close_data()
|
|
||||||
if self.state != _DID_DATA:
|
|
||||||
raise Error('Read resource data at wrong time')
|
|
||||||
if n:
|
|
||||||
n = n[0]
|
|
||||||
n = min(n, self.rlen)
|
|
||||||
else:
|
|
||||||
n = self.rlen
|
|
||||||
self.rlen = self.rlen - n
|
|
||||||
return self._read(n)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.state is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if self.rlen:
|
|
||||||
dummy = self.read_rsrc(self.rlen)
|
|
||||||
self._checkcrc()
|
|
||||||
finally:
|
|
||||||
self.state = None
|
|
||||||
self.ifp.close()
|
|
||||||
|
|
||||||
def hexbin(inp, out):
|
|
||||||
"""hexbin(infilename, outfilename) - Decode binhexed file"""
|
|
||||||
ifp = HexBin(inp)
|
|
||||||
finfo = ifp.FInfo
|
|
||||||
if not out:
|
|
||||||
out = ifp.FName
|
|
||||||
|
|
||||||
with io.open(out, 'wb') as ofp:
|
|
||||||
# XXXX Do translation on non-mac systems
|
|
||||||
while True:
|
|
||||||
d = ifp.read(128000)
|
|
||||||
if not d: break
|
|
||||||
ofp.write(d)
|
|
||||||
ifp.close_data()
|
|
||||||
|
|
||||||
d = ifp.read_rsrc(128000)
|
|
||||||
if d:
|
|
||||||
ofp = openrsrc(out, 'wb')
|
|
||||||
ofp.write(d)
|
|
||||||
while True:
|
|
||||||
d = ifp.read_rsrc(128000)
|
|
||||||
if not d: break
|
|
||||||
ofp.write(d)
|
|
||||||
ofp.close()
|
|
||||||
|
|
||||||
ifp.close()
|
|
||||||
10
Lib/bisect.py
vendored
10
Lib/bisect.py
vendored
@@ -8,6 +8,8 @@ def insort_right(a, x, lo=0, hi=None, *, key=None):
|
|||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
Optional args lo (default 0) and hi (default len(a)) bound the
|
||||||
slice of a to be searched.
|
slice of a to be searched.
|
||||||
|
|
||||||
|
A custom key function can be supplied to customize the sort order.
|
||||||
"""
|
"""
|
||||||
if key is None:
|
if key is None:
|
||||||
lo = bisect_right(a, x, lo, hi)
|
lo = bisect_right(a, x, lo, hi)
|
||||||
@@ -25,6 +27,8 @@ def bisect_right(a, x, lo=0, hi=None, *, key=None):
|
|||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
Optional args lo (default 0) and hi (default len(a)) bound the
|
||||||
slice of a to be searched.
|
slice of a to be searched.
|
||||||
|
|
||||||
|
A custom key function can be supplied to customize the sort order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if lo < 0:
|
if lo < 0:
|
||||||
@@ -57,6 +61,8 @@ def insort_left(a, x, lo=0, hi=None, *, key=None):
|
|||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
Optional args lo (default 0) and hi (default len(a)) bound the
|
||||||
slice of a to be searched.
|
slice of a to be searched.
|
||||||
|
|
||||||
|
A custom key function can be supplied to customize the sort order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if key is None:
|
if key is None:
|
||||||
@@ -74,6 +80,8 @@ def bisect_left(a, x, lo=0, hi=None, *, key=None):
|
|||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
Optional args lo (default 0) and hi (default len(a)) bound the
|
||||||
slice of a to be searched.
|
slice of a to be searched.
|
||||||
|
|
||||||
|
A custom key function can be supplied to customize the sort order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if lo < 0:
|
if lo < 0:
|
||||||
@@ -107,4 +115,4 @@ except ImportError:
|
|||||||
|
|
||||||
# Create aliases
|
# Create aliases
|
||||||
bisect = bisect_right
|
bisect = bisect_right
|
||||||
insort = insort_right
|
insort = insort_right
|
||||||
|
|||||||
128
Lib/calendar.py
vendored
128
Lib/calendar.py
vendored
@@ -7,15 +7,22 @@ set the first day of the week (0=Monday, 6=Sunday)."""
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
|
from enum import IntEnum, global_enum
|
||||||
import locale as _locale
|
import locale as _locale
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
import warnings
|
||||||
|
|
||||||
__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
|
__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
|
||||||
"firstweekday", "isleap", "leapdays", "weekday", "monthrange",
|
"firstweekday", "isleap", "leapdays", "weekday", "monthrange",
|
||||||
"monthcalendar", "prmonth", "month", "prcal", "calendar",
|
"monthcalendar", "prmonth", "month", "prcal", "calendar",
|
||||||
"timegm", "month_name", "month_abbr", "day_name", "day_abbr",
|
"timegm", "month_name", "month_abbr", "day_name", "day_abbr",
|
||||||
"Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
|
"Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
|
||||||
"LocaleHTMLCalendar", "weekheader"]
|
"LocaleHTMLCalendar", "weekheader",
|
||||||
|
"Day", "Month", "JANUARY", "FEBRUARY", "MARCH",
|
||||||
|
"APRIL", "MAY", "JUNE", "JULY",
|
||||||
|
"AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
|
||||||
|
"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",
|
||||||
|
"SATURDAY", "SUNDAY"]
|
||||||
|
|
||||||
# Exception raised for bad input (with string parameter for details)
|
# Exception raised for bad input (with string parameter for details)
|
||||||
error = ValueError
|
error = ValueError
|
||||||
@@ -35,9 +42,46 @@ class IllegalWeekdayError(ValueError):
|
|||||||
return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
|
return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
|
||||||
|
|
||||||
|
|
||||||
# Constants for months referenced later
|
def __getattr__(name):
|
||||||
January = 1
|
if name in ('January', 'February'):
|
||||||
February = 2
|
warnings.warn(f"The '{name}' attribute is deprecated, use '{name.upper()}' instead",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
if name == 'January':
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 2
|
||||||
|
|
||||||
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
||||||
|
# Constants for months
|
||||||
|
@global_enum
|
||||||
|
class Month(IntEnum):
|
||||||
|
JANUARY = 1
|
||||||
|
FEBRUARY = 2
|
||||||
|
MARCH = 3
|
||||||
|
APRIL = 4
|
||||||
|
MAY = 5
|
||||||
|
JUNE = 6
|
||||||
|
JULY = 7
|
||||||
|
AUGUST = 8
|
||||||
|
SEPTEMBER = 9
|
||||||
|
OCTOBER = 10
|
||||||
|
NOVEMBER = 11
|
||||||
|
DECEMBER = 12
|
||||||
|
|
||||||
|
|
||||||
|
# Constants for days
|
||||||
|
@global_enum
|
||||||
|
class Day(IntEnum):
|
||||||
|
MONDAY = 0
|
||||||
|
TUESDAY = 1
|
||||||
|
WEDNESDAY = 2
|
||||||
|
THURSDAY = 3
|
||||||
|
FRIDAY = 4
|
||||||
|
SATURDAY = 5
|
||||||
|
SUNDAY = 6
|
||||||
|
|
||||||
|
|
||||||
# Number of days per month (except for February in leap years)
|
# Number of days per month (except for February in leap years)
|
||||||
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||||
@@ -93,9 +137,6 @@ day_abbr = _localized_day('%a')
|
|||||||
month_name = _localized_month('%B')
|
month_name = _localized_month('%B')
|
||||||
month_abbr = _localized_month('%b')
|
month_abbr = _localized_month('%b')
|
||||||
|
|
||||||
# Constants for weekdays
|
|
||||||
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
|
|
||||||
|
|
||||||
|
|
||||||
def isleap(year):
|
def isleap(year):
|
||||||
"""Return True for leap years, False for non-leap years."""
|
"""Return True for leap years, False for non-leap years."""
|
||||||
@@ -114,7 +155,7 @@ def weekday(year, month, day):
|
|||||||
"""Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
|
"""Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
|
||||||
if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
|
if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
|
||||||
year = 2000 + year % 400
|
year = 2000 + year % 400
|
||||||
return datetime.date(year, month, day).weekday()
|
return Day(datetime.date(year, month, day).weekday())
|
||||||
|
|
||||||
|
|
||||||
def monthrange(year, month):
|
def monthrange(year, month):
|
||||||
@@ -123,12 +164,12 @@ def monthrange(year, month):
|
|||||||
if not 1 <= month <= 12:
|
if not 1 <= month <= 12:
|
||||||
raise IllegalMonthError(month)
|
raise IllegalMonthError(month)
|
||||||
day1 = weekday(year, month, 1)
|
day1 = weekday(year, month, 1)
|
||||||
ndays = mdays[month] + (month == February and isleap(year))
|
ndays = mdays[month] + (month == FEBRUARY and isleap(year))
|
||||||
return day1, ndays
|
return day1, ndays
|
||||||
|
|
||||||
|
|
||||||
def _monthlen(year, month):
|
def _monthlen(year, month):
|
||||||
return mdays[month] + (month == February and isleap(year))
|
return mdays[month] + (month == FEBRUARY and isleap(year))
|
||||||
|
|
||||||
|
|
||||||
def _prevmonth(year, month):
|
def _prevmonth(year, month):
|
||||||
@@ -258,10 +299,7 @@ class Calendar(object):
|
|||||||
Each month contains between 4 and 6 weeks and each week contains 1-7
|
Each month contains between 4 and 6 weeks and each week contains 1-7
|
||||||
days. Days are datetime.date objects.
|
days. Days are datetime.date objects.
|
||||||
"""
|
"""
|
||||||
months = [
|
months = [self.monthdatescalendar(year, m) for m in Month]
|
||||||
self.monthdatescalendar(year, i)
|
|
||||||
for i in range(January, January+12)
|
|
||||||
]
|
|
||||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
return [months[i:i+width] for i in range(0, len(months), width) ]
|
||||||
|
|
||||||
def yeardays2calendar(self, year, width=3):
|
def yeardays2calendar(self, year, width=3):
|
||||||
@@ -271,10 +309,7 @@ class Calendar(object):
|
|||||||
(day number, weekday number) tuples. Day numbers outside this month are
|
(day number, weekday number) tuples. Day numbers outside this month are
|
||||||
zero.
|
zero.
|
||||||
"""
|
"""
|
||||||
months = [
|
months = [self.monthdays2calendar(year, m) for m in Month]
|
||||||
self.monthdays2calendar(year, i)
|
|
||||||
for i in range(January, January+12)
|
|
||||||
]
|
|
||||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
return [months[i:i+width] for i in range(0, len(months), width) ]
|
||||||
|
|
||||||
def yeardayscalendar(self, year, width=3):
|
def yeardayscalendar(self, year, width=3):
|
||||||
@@ -283,10 +318,7 @@ class Calendar(object):
|
|||||||
yeardatescalendar()). Entries in the week lists are day numbers.
|
yeardatescalendar()). Entries in the week lists are day numbers.
|
||||||
Day numbers outside this month are zero.
|
Day numbers outside this month are zero.
|
||||||
"""
|
"""
|
||||||
months = [
|
months = [self.monthdayscalendar(year, m) for m in Month]
|
||||||
self.monthdayscalendar(year, i)
|
|
||||||
for i in range(January, January+12)
|
|
||||||
]
|
|
||||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
return [months[i:i+width] for i in range(0, len(months), width) ]
|
||||||
|
|
||||||
|
|
||||||
@@ -507,7 +539,7 @@ class HTMLCalendar(Calendar):
|
|||||||
a('\n')
|
a('\n')
|
||||||
a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
|
a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
|
||||||
width, self.cssclass_year_head, theyear))
|
width, self.cssclass_year_head, theyear))
|
||||||
for i in range(January, January+12, width):
|
for i in range(JANUARY, JANUARY+12, width):
|
||||||
# months in this row
|
# months in this row
|
||||||
months = range(i, min(i+width, 13))
|
months = range(i, min(i+width, 13))
|
||||||
a('<tr>')
|
a('<tr>')
|
||||||
@@ -546,71 +578,67 @@ class HTMLCalendar(Calendar):
|
|||||||
class different_locale:
|
class different_locale:
|
||||||
def __init__(self, locale):
|
def __init__(self, locale):
|
||||||
self.locale = locale
|
self.locale = locale
|
||||||
|
self.oldlocale = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.oldlocale = _locale.getlocale(_locale.LC_TIME)
|
self.oldlocale = _locale.setlocale(_locale.LC_TIME, None)
|
||||||
_locale.setlocale(_locale.LC_TIME, self.locale)
|
_locale.setlocale(_locale.LC_TIME, self.locale)
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
|
if self.oldlocale is None:
|
||||||
|
return
|
||||||
_locale.setlocale(_locale.LC_TIME, self.oldlocale)
|
_locale.setlocale(_locale.LC_TIME, self.oldlocale)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_default_locale():
|
||||||
|
locale = _locale.setlocale(_locale.LC_TIME, None)
|
||||||
|
if locale == "C":
|
||||||
|
with different_locale(""):
|
||||||
|
# The LC_TIME locale does not seem to be configured:
|
||||||
|
# get the user preferred locale.
|
||||||
|
locale = _locale.setlocale(_locale.LC_TIME, None)
|
||||||
|
return locale
|
||||||
|
|
||||||
|
|
||||||
class LocaleTextCalendar(TextCalendar):
|
class LocaleTextCalendar(TextCalendar):
|
||||||
"""
|
"""
|
||||||
This class can be passed a locale name in the constructor and will return
|
This class can be passed a locale name in the constructor and will return
|
||||||
month and weekday names in the specified locale. If this locale includes
|
month and weekday names in the specified locale.
|
||||||
an encoding all strings containing month and weekday names will be returned
|
|
||||||
as unicode.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, firstweekday=0, locale=None):
|
def __init__(self, firstweekday=0, locale=None):
|
||||||
TextCalendar.__init__(self, firstweekday)
|
TextCalendar.__init__(self, firstweekday)
|
||||||
if locale is None:
|
if locale is None:
|
||||||
locale = _locale.getdefaultlocale()
|
locale = _get_default_locale()
|
||||||
self.locale = locale
|
self.locale = locale
|
||||||
|
|
||||||
def formatweekday(self, day, width):
|
def formatweekday(self, day, width):
|
||||||
with different_locale(self.locale):
|
with different_locale(self.locale):
|
||||||
if width >= 9:
|
return super().formatweekday(day, width)
|
||||||
names = day_name
|
|
||||||
else:
|
|
||||||
names = day_abbr
|
|
||||||
name = names[day]
|
|
||||||
return name[:width].center(width)
|
|
||||||
|
|
||||||
def formatmonthname(self, theyear, themonth, width, withyear=True):
|
def formatmonthname(self, theyear, themonth, width, withyear=True):
|
||||||
with different_locale(self.locale):
|
with different_locale(self.locale):
|
||||||
s = month_name[themonth]
|
return super().formatmonthname(theyear, themonth, width, withyear)
|
||||||
if withyear:
|
|
||||||
s = "%s %r" % (s, theyear)
|
|
||||||
return s.center(width)
|
|
||||||
|
|
||||||
|
|
||||||
class LocaleHTMLCalendar(HTMLCalendar):
|
class LocaleHTMLCalendar(HTMLCalendar):
|
||||||
"""
|
"""
|
||||||
This class can be passed a locale name in the constructor and will return
|
This class can be passed a locale name in the constructor and will return
|
||||||
month and weekday names in the specified locale. If this locale includes
|
month and weekday names in the specified locale.
|
||||||
an encoding all strings containing month and weekday names will be returned
|
|
||||||
as unicode.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, firstweekday=0, locale=None):
|
def __init__(self, firstweekday=0, locale=None):
|
||||||
HTMLCalendar.__init__(self, firstweekday)
|
HTMLCalendar.__init__(self, firstweekday)
|
||||||
if locale is None:
|
if locale is None:
|
||||||
locale = _locale.getdefaultlocale()
|
locale = _get_default_locale()
|
||||||
self.locale = locale
|
self.locale = locale
|
||||||
|
|
||||||
def formatweekday(self, day):
|
def formatweekday(self, day):
|
||||||
with different_locale(self.locale):
|
with different_locale(self.locale):
|
||||||
s = day_abbr[day]
|
return super().formatweekday(day)
|
||||||
return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
|
|
||||||
|
|
||||||
def formatmonthname(self, theyear, themonth, withyear=True):
|
def formatmonthname(self, theyear, themonth, withyear=True):
|
||||||
with different_locale(self.locale):
|
with different_locale(self.locale):
|
||||||
s = month_name[themonth]
|
return super().formatmonthname(theyear, themonth, withyear)
|
||||||
if withyear:
|
|
||||||
s = '%s %s' % (s, theyear)
|
|
||||||
return '<tr><th colspan="7" class="month">%s</th></tr>' % s
|
|
||||||
|
|
||||||
|
|
||||||
# Support for old module level interface
|
# Support for old module level interface
|
||||||
c = TextCalendar()
|
c = TextCalendar()
|
||||||
@@ -695,7 +723,7 @@ def main(args):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-L", "--locale",
|
"-L", "--locale",
|
||||||
default=None,
|
default=None,
|
||||||
help="locale to be used from month and weekday names"
|
help="locale to use for month and weekday names"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-e", "--encoding",
|
"-e", "--encoding",
|
||||||
|
|||||||
46
Lib/cgi.py
vendored
46
Lib/cgi.py
vendored
@@ -13,6 +13,11 @@
|
|||||||
|
|
||||||
This module defines a number of utilities for use by CGI scripts
|
This module defines a number of utilities for use by CGI scripts
|
||||||
written in Python.
|
written in Python.
|
||||||
|
|
||||||
|
The global variable maxlen can be set to an integer indicating the maximum size
|
||||||
|
of a POST request. POST requests larger than this size will result in a
|
||||||
|
ValueError being raised during parsing. The default value of this variable is 0,
|
||||||
|
meaning the request size is unlimited.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# History
|
# History
|
||||||
@@ -41,12 +46,16 @@ from email.message import Message
|
|||||||
import html
|
import html
|
||||||
import locale
|
import locale
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
__all__ = ["MiniFieldStorage", "FieldStorage", "parse", "parse_multipart",
|
__all__ = ["MiniFieldStorage", "FieldStorage", "parse", "parse_multipart",
|
||||||
"parse_header", "test", "print_exception", "print_environ",
|
"parse_header", "test", "print_exception", "print_environ",
|
||||||
"print_form", "print_directory", "print_arguments",
|
"print_form", "print_directory", "print_arguments",
|
||||||
"print_environ_usage"]
|
"print_environ_usage"]
|
||||||
|
|
||||||
|
|
||||||
|
warnings._deprecated(__name__, remove=(3,13))
|
||||||
|
|
||||||
# Logging support
|
# Logging support
|
||||||
# ===============
|
# ===============
|
||||||
|
|
||||||
@@ -77,9 +86,11 @@ def initlog(*allargs):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
global log, logfile, logfp
|
global log, logfile, logfp
|
||||||
|
warnings.warn("cgi.log() is deprecated as of 3.10. Use logging instead",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
if logfile and not logfp:
|
if logfile and not logfp:
|
||||||
try:
|
try:
|
||||||
logfp = open(logfile, "a")
|
logfp = open(logfile, "a", encoding="locale")
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if not logfp:
|
if not logfp:
|
||||||
@@ -115,7 +126,8 @@ log = initlog # The current logging function
|
|||||||
# 0 ==> unlimited input
|
# 0 ==> unlimited input
|
||||||
maxlen = 0
|
maxlen = 0
|
||||||
|
|
||||||
def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
def parse(fp=None, environ=os.environ, keep_blank_values=0,
|
||||||
|
strict_parsing=0, separator='&'):
|
||||||
"""Parse a query in the environment or from a file (default stdin)
|
"""Parse a query in the environment or from a file (default stdin)
|
||||||
|
|
||||||
Arguments, all optional:
|
Arguments, all optional:
|
||||||
@@ -134,6 +146,9 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
|||||||
strict_parsing: flag indicating what to do with parsing errors.
|
strict_parsing: flag indicating what to do with parsing errors.
|
||||||
If false (the default), errors are silently ignored.
|
If false (the default), errors are silently ignored.
|
||||||
If true, errors raise a ValueError exception.
|
If true, errors raise a ValueError exception.
|
||||||
|
|
||||||
|
separator: str. The symbol to use for separating the query arguments.
|
||||||
|
Defaults to &.
|
||||||
"""
|
"""
|
||||||
if fp is None:
|
if fp is None:
|
||||||
fp = sys.stdin
|
fp = sys.stdin
|
||||||
@@ -154,7 +169,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
|||||||
if environ['REQUEST_METHOD'] == 'POST':
|
if environ['REQUEST_METHOD'] == 'POST':
|
||||||
ctype, pdict = parse_header(environ['CONTENT_TYPE'])
|
ctype, pdict = parse_header(environ['CONTENT_TYPE'])
|
||||||
if ctype == 'multipart/form-data':
|
if ctype == 'multipart/form-data':
|
||||||
return parse_multipart(fp, pdict)
|
return parse_multipart(fp, pdict, separator=separator)
|
||||||
elif ctype == 'application/x-www-form-urlencoded':
|
elif ctype == 'application/x-www-form-urlencoded':
|
||||||
clength = int(environ['CONTENT_LENGTH'])
|
clength = int(environ['CONTENT_LENGTH'])
|
||||||
if maxlen and clength > maxlen:
|
if maxlen and clength > maxlen:
|
||||||
@@ -178,10 +193,10 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
|||||||
qs = ""
|
qs = ""
|
||||||
environ['QUERY_STRING'] = qs # XXX Shouldn't, really
|
environ['QUERY_STRING'] = qs # XXX Shouldn't, really
|
||||||
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing,
|
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing,
|
||||||
encoding=encoding)
|
encoding=encoding, separator=separator)
|
||||||
|
|
||||||
|
|
||||||
def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"):
|
def parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator='&'):
|
||||||
"""Parse multipart input.
|
"""Parse multipart input.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -194,15 +209,18 @@ def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"):
|
|||||||
value is a list of values for that field. For non-file fields, the value
|
value is a list of values for that field. For non-file fields, the value
|
||||||
is a list of strings.
|
is a list of strings.
|
||||||
"""
|
"""
|
||||||
# RFC 2026, Section 5.1 : The "multipart" boundary delimiters are always
|
# RFC 2046, Section 5.1 : The "multipart" boundary delimiters are always
|
||||||
# represented as 7bit US-ASCII.
|
# represented as 7bit US-ASCII.
|
||||||
boundary = pdict['boundary'].decode('ascii')
|
boundary = pdict['boundary'].decode('ascii')
|
||||||
ctype = "multipart/form-data; boundary={}".format(boundary)
|
ctype = "multipart/form-data; boundary={}".format(boundary)
|
||||||
headers = Message()
|
headers = Message()
|
||||||
headers.set_type(ctype)
|
headers.set_type(ctype)
|
||||||
headers['Content-Length'] = pdict['CONTENT-LENGTH']
|
try:
|
||||||
|
headers['Content-Length'] = pdict['CONTENT-LENGTH']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
fs = FieldStorage(fp, headers=headers, encoding=encoding, errors=errors,
|
fs = FieldStorage(fp, headers=headers, encoding=encoding, errors=errors,
|
||||||
environ={'REQUEST_METHOD': 'POST'})
|
environ={'REQUEST_METHOD': 'POST'}, separator=separator)
|
||||||
return {k: fs.getlist(k) for k in fs}
|
return {k: fs.getlist(k) for k in fs}
|
||||||
|
|
||||||
def _parseparam(s):
|
def _parseparam(s):
|
||||||
@@ -312,7 +330,7 @@ class FieldStorage:
|
|||||||
def __init__(self, fp=None, headers=None, outerboundary=b'',
|
def __init__(self, fp=None, headers=None, outerboundary=b'',
|
||||||
environ=os.environ, keep_blank_values=0, strict_parsing=0,
|
environ=os.environ, keep_blank_values=0, strict_parsing=0,
|
||||||
limit=None, encoding='utf-8', errors='replace',
|
limit=None, encoding='utf-8', errors='replace',
|
||||||
max_num_fields=None):
|
max_num_fields=None, separator='&'):
|
||||||
"""Constructor. Read multipart/* until last part.
|
"""Constructor. Read multipart/* until last part.
|
||||||
|
|
||||||
Arguments, all optional:
|
Arguments, all optional:
|
||||||
@@ -360,6 +378,7 @@ class FieldStorage:
|
|||||||
self.keep_blank_values = keep_blank_values
|
self.keep_blank_values = keep_blank_values
|
||||||
self.strict_parsing = strict_parsing
|
self.strict_parsing = strict_parsing
|
||||||
self.max_num_fields = max_num_fields
|
self.max_num_fields = max_num_fields
|
||||||
|
self.separator = separator
|
||||||
if 'REQUEST_METHOD' in environ:
|
if 'REQUEST_METHOD' in environ:
|
||||||
method = environ['REQUEST_METHOD'].upper()
|
method = environ['REQUEST_METHOD'].upper()
|
||||||
self.qs_on_post = None
|
self.qs_on_post = None
|
||||||
@@ -586,7 +605,7 @@ class FieldStorage:
|
|||||||
query = urllib.parse.parse_qsl(
|
query = urllib.parse.parse_qsl(
|
||||||
qs, self.keep_blank_values, self.strict_parsing,
|
qs, self.keep_blank_values, self.strict_parsing,
|
||||||
encoding=self.encoding, errors=self.errors,
|
encoding=self.encoding, errors=self.errors,
|
||||||
max_num_fields=self.max_num_fields)
|
max_num_fields=self.max_num_fields, separator=self.separator)
|
||||||
self.list = [MiniFieldStorage(key, value) for key, value in query]
|
self.list = [MiniFieldStorage(key, value) for key, value in query]
|
||||||
self.skip_lines()
|
self.skip_lines()
|
||||||
|
|
||||||
@@ -602,7 +621,7 @@ class FieldStorage:
|
|||||||
query = urllib.parse.parse_qsl(
|
query = urllib.parse.parse_qsl(
|
||||||
self.qs_on_post, self.keep_blank_values, self.strict_parsing,
|
self.qs_on_post, self.keep_blank_values, self.strict_parsing,
|
||||||
encoding=self.encoding, errors=self.errors,
|
encoding=self.encoding, errors=self.errors,
|
||||||
max_num_fields=self.max_num_fields)
|
max_num_fields=self.max_num_fields, separator=self.separator)
|
||||||
self.list.extend(MiniFieldStorage(key, value) for key, value in query)
|
self.list.extend(MiniFieldStorage(key, value) for key, value in query)
|
||||||
|
|
||||||
klass = self.FieldStorageClass or self.__class__
|
klass = self.FieldStorageClass or self.__class__
|
||||||
@@ -646,7 +665,7 @@ class FieldStorage:
|
|||||||
else self.limit - self.bytes_read
|
else self.limit - self.bytes_read
|
||||||
part = klass(self.fp, headers, ib, environ, keep_blank_values,
|
part = klass(self.fp, headers, ib, environ, keep_blank_values,
|
||||||
strict_parsing, limit,
|
strict_parsing, limit,
|
||||||
self.encoding, self.errors, max_num_fields)
|
self.encoding, self.errors, max_num_fields, self.separator)
|
||||||
|
|
||||||
if max_num_fields is not None:
|
if max_num_fields is not None:
|
||||||
max_num_fields -= 1
|
max_num_fields -= 1
|
||||||
@@ -736,7 +755,8 @@ class FieldStorage:
|
|||||||
last_line_lfend = True
|
last_line_lfend = True
|
||||||
_read = 0
|
_read = 0
|
||||||
while 1:
|
while 1:
|
||||||
if self.limit is not None and _read >= self.limit:
|
|
||||||
|
if self.limit is not None and 0 <= self.limit <= _read:
|
||||||
break
|
break
|
||||||
line = self.fp.readline(1<<16) # bytes
|
line = self.fp.readline(1<<16) # bytes
|
||||||
self.bytes_read += len(line)
|
self.bytes_read += len(line)
|
||||||
|
|||||||
25
Lib/cgitb.py
vendored
25
Lib/cgitb.py
vendored
@@ -31,6 +31,11 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
import tokenize
|
import tokenize
|
||||||
import traceback
|
import traceback
|
||||||
|
import warnings
|
||||||
|
from html import escape as html_escape
|
||||||
|
|
||||||
|
warnings._deprecated(__name__, remove=(3, 13))
|
||||||
|
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
"""Return a string that resets the CGI and browser to a known state."""
|
"""Return a string that resets the CGI and browser to a known state."""
|
||||||
@@ -69,7 +74,7 @@ def lookup(name, frame, locals):
|
|||||||
return 'global', frame.f_globals[name]
|
return 'global', frame.f_globals[name]
|
||||||
if '__builtins__' in frame.f_globals:
|
if '__builtins__' in frame.f_globals:
|
||||||
builtins = frame.f_globals['__builtins__']
|
builtins = frame.f_globals['__builtins__']
|
||||||
if type(builtins) is type({}):
|
if isinstance(builtins, dict):
|
||||||
if name in builtins:
|
if name in builtins:
|
||||||
return 'builtin', builtins[name]
|
return 'builtin', builtins[name]
|
||||||
else:
|
else:
|
||||||
@@ -105,10 +110,16 @@ def html(einfo, context=5):
|
|||||||
etype = etype.__name__
|
etype = etype.__name__
|
||||||
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
|
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
|
||||||
date = time.ctime(time.time())
|
date = time.ctime(time.time())
|
||||||
head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading(
|
head = f'''
|
||||||
'<big><big>%s</big></big>' %
|
<body bgcolor="#f0f0f8">
|
||||||
strong(pydoc.html.escape(str(etype))),
|
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||||
'#ffffff', '#6622aa', pyver + '<br>' + date) + '''
|
<tr bgcolor="#6622aa">
|
||||||
|
<td valign=bottom> <br>
|
||||||
|
<font color="#ffffff" face="helvetica, arial"> <br>
|
||||||
|
<big><big><strong>{html_escape(str(etype))}</strong></big></big></font></td>
|
||||||
|
<td align=right valign=bottom>
|
||||||
|
<font color="#ffffff" face="helvetica, arial">{pyver}<br>{date}</font></td>
|
||||||
|
</tr></table>
|
||||||
<p>A problem occurred in a Python script. Here is the sequence of
|
<p>A problem occurred in a Python script. Here is the sequence of
|
||||||
function calls leading up to the error, in the order they occurred.</p>'''
|
function calls leading up to the error, in the order they occurred.</p>'''
|
||||||
|
|
||||||
@@ -181,8 +192,8 @@ function calls leading up to the error, in the order they occurred.</p>'''
|
|||||||
|
|
||||||
|
|
||||||
<!-- The above is a description of an error in a Python program, formatted
|
<!-- The above is a description of an error in a Python program, formatted
|
||||||
for a Web browser because the 'cgitb' module was enabled. In case you
|
for a web browser because the 'cgitb' module was enabled. In case you
|
||||||
are not reading this in a Web browser, here is the original traceback:
|
are not reading this in a web browser, here is the original traceback:
|
||||||
|
|
||||||
%s
|
%s
|
||||||
-->
|
-->
|
||||||
|
|||||||
6
Lib/chunk.py
vendored
6
Lib/chunk.py
vendored
@@ -48,6 +48,10 @@ specifies whether or not chunks are aligned on 2-byte boundaries. The
|
|||||||
default is 1, i.e. aligned.
|
default is 1, i.e. aligned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings._deprecated(__name__, remove=(3, 13))
|
||||||
|
|
||||||
class Chunk:
|
class Chunk:
|
||||||
def __init__(self, file, align=True, bigendian=True, inclheader=False):
|
def __init__(self, file, align=True, bigendian=True, inclheader=False):
|
||||||
import struct
|
import struct
|
||||||
@@ -64,7 +68,7 @@ class Chunk:
|
|||||||
try:
|
try:
|
||||||
self.chunksize = struct.unpack_from(strflag+'L', file.read(4))[0]
|
self.chunksize = struct.unpack_from(strflag+'L', file.read(4))[0]
|
||||||
except struct.error:
|
except struct.error:
|
||||||
raise EOFError
|
raise EOFError from None
|
||||||
if inclheader:
|
if inclheader:
|
||||||
self.chunksize = self.chunksize - 8 # subtract header
|
self.chunksize = self.chunksize - 8 # subtract header
|
||||||
self.size_read = 0
|
self.size_read = 0
|
||||||
|
|||||||
10
Lib/cmd.py
vendored
10
Lib/cmd.py
vendored
@@ -310,10 +310,10 @@ class Cmd:
|
|||||||
names = self.get_names()
|
names = self.get_names()
|
||||||
cmds_doc = []
|
cmds_doc = []
|
||||||
cmds_undoc = []
|
cmds_undoc = []
|
||||||
help = {}
|
topics = set()
|
||||||
for name in names:
|
for name in names:
|
||||||
if name[:5] == 'help_':
|
if name[:5] == 'help_':
|
||||||
help[name[5:]]=1
|
topics.add(name[5:])
|
||||||
names.sort()
|
names.sort()
|
||||||
# There can be duplicates if routines overridden
|
# There can be duplicates if routines overridden
|
||||||
prevname = ''
|
prevname = ''
|
||||||
@@ -323,16 +323,16 @@ class Cmd:
|
|||||||
continue
|
continue
|
||||||
prevname = name
|
prevname = name
|
||||||
cmd=name[3:]
|
cmd=name[3:]
|
||||||
if cmd in help:
|
if cmd in topics:
|
||||||
cmds_doc.append(cmd)
|
cmds_doc.append(cmd)
|
||||||
del help[cmd]
|
topics.remove(cmd)
|
||||||
elif getattr(self, name).__doc__:
|
elif getattr(self, name).__doc__:
|
||||||
cmds_doc.append(cmd)
|
cmds_doc.append(cmd)
|
||||||
else:
|
else:
|
||||||
cmds_undoc.append(cmd)
|
cmds_undoc.append(cmd)
|
||||||
self.stdout.write("%s\n"%str(self.doc_leader))
|
self.stdout.write("%s\n"%str(self.doc_leader))
|
||||||
self.print_topics(self.doc_header, cmds_doc, 15,80)
|
self.print_topics(self.doc_header, cmds_doc, 15,80)
|
||||||
self.print_topics(self.misc_header, list(help.keys()),15,80)
|
self.print_topics(self.misc_header, sorted(topics),15,80)
|
||||||
self.print_topics(self.undoc_header, cmds_undoc, 15,80)
|
self.print_topics(self.undoc_header, cmds_undoc, 15,80)
|
||||||
|
|
||||||
def print_topics(self, header, cmds, cmdlen, maxcol):
|
def print_topics(self, header, cmds, cmdlen, maxcol):
|
||||||
|
|||||||
9
Lib/code.py
vendored
9
Lib/code.py
vendored
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import argparse
|
|
||||||
from codeop import CommandCompiler, compile_command
|
from codeop import CommandCompiler, compile_command
|
||||||
|
|
||||||
__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
|
__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
|
||||||
@@ -41,7 +40,7 @@ class InteractiveInterpreter:
|
|||||||
|
|
||||||
Arguments are as for compile_command().
|
Arguments are as for compile_command().
|
||||||
|
|
||||||
One several things can happen:
|
One of several things can happen:
|
||||||
|
|
||||||
1) The input is incorrect; compile_command() raised an
|
1) The input is incorrect; compile_command() raised an
|
||||||
exception (SyntaxError or OverflowError). A syntax traceback
|
exception (SyntaxError or OverflowError). A syntax traceback
|
||||||
@@ -107,6 +106,7 @@ class InteractiveInterpreter:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
type, value, tb = sys.exc_info()
|
type, value, tb = sys.exc_info()
|
||||||
|
sys.last_exc = value
|
||||||
sys.last_type = type
|
sys.last_type = type
|
||||||
sys.last_value = value
|
sys.last_value = value
|
||||||
sys.last_traceback = tb
|
sys.last_traceback = tb
|
||||||
@@ -120,7 +120,7 @@ class InteractiveInterpreter:
|
|||||||
else:
|
else:
|
||||||
# Stuff in the right filename
|
# Stuff in the right filename
|
||||||
value = SyntaxError(msg, (filename, lineno, offset, line))
|
value = SyntaxError(msg, (filename, lineno, offset, line))
|
||||||
sys.last_value = value
|
sys.last_exc = sys.last_value = value
|
||||||
if sys.excepthook is sys.__excepthook__:
|
if sys.excepthook is sys.__excepthook__:
|
||||||
lines = traceback.format_exception_only(type, value)
|
lines = traceback.format_exception_only(type, value)
|
||||||
self.write(''.join(lines))
|
self.write(''.join(lines))
|
||||||
@@ -139,6 +139,7 @@ class InteractiveInterpreter:
|
|||||||
"""
|
"""
|
||||||
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
|
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
|
||||||
sys.last_traceback = last_tb
|
sys.last_traceback = last_tb
|
||||||
|
sys.last_exc = ei[1]
|
||||||
try:
|
try:
|
||||||
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
|
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
|
||||||
if sys.excepthook is sys.__excepthook__:
|
if sys.excepthook is sys.__excepthook__:
|
||||||
@@ -303,6 +304,8 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-q', action='store_true',
|
parser.add_argument('-q', action='store_true',
|
||||||
help="don't print version and copyright messages")
|
help="don't print version and copyright messages")
|
||||||
|
|||||||
25
Lib/codecs.py
vendored
25
Lib/codecs.py
vendored
@@ -414,6 +414,9 @@ class StreamWriter(Codec):
|
|||||||
def __exit__(self, type, value, tb):
|
def __exit__(self, type, value, tb):
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
|
|
||||||
|
def __reduce_ex__(self, proto):
|
||||||
|
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
class StreamReader(Codec):
|
class StreamReader(Codec):
|
||||||
@@ -663,6 +666,9 @@ class StreamReader(Codec):
|
|||||||
def __exit__(self, type, value, tb):
|
def __exit__(self, type, value, tb):
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
|
|
||||||
|
def __reduce_ex__(self, proto):
|
||||||
|
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
class StreamReaderWriter:
|
class StreamReaderWriter:
|
||||||
@@ -750,6 +756,9 @@ class StreamReaderWriter:
|
|||||||
def __exit__(self, type, value, tb):
|
def __exit__(self, type, value, tb):
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
|
|
||||||
|
def __reduce_ex__(self, proto):
|
||||||
|
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
class StreamRecoder:
|
class StreamRecoder:
|
||||||
@@ -866,6 +875,9 @@ class StreamRecoder:
|
|||||||
def __exit__(self, type, value, tb):
|
def __exit__(self, type, value, tb):
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
|
|
||||||
|
def __reduce_ex__(self, proto):
|
||||||
|
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||||
|
|
||||||
### Shortcuts
|
### Shortcuts
|
||||||
|
|
||||||
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
|
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
|
||||||
@@ -878,7 +890,8 @@ def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
|
|||||||
codecs. Output is also codec dependent and will usually be
|
codecs. Output is also codec dependent and will usually be
|
||||||
Unicode as well.
|
Unicode as well.
|
||||||
|
|
||||||
Underlying encoded files are always opened in binary mode.
|
If encoding is not None, then the
|
||||||
|
underlying encoded files are always opened in binary mode.
|
||||||
The default file mode is 'r', meaning to open the file in read mode.
|
The default file mode is 'r', meaning to open the file in read mode.
|
||||||
|
|
||||||
encoding specifies the encoding which is to be used for the
|
encoding specifies the encoding which is to be used for the
|
||||||
@@ -1114,13 +1127,3 @@ except LookupError:
|
|||||||
_false = 0
|
_false = 0
|
||||||
if _false:
|
if _false:
|
||||||
import encodings
|
import encodings
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
# Make stdout translate Latin-1 output into UTF-8 output
|
|
||||||
sys.stdout = EncodedFile(sys.stdout, 'latin-1', 'utf-8')
|
|
||||||
|
|
||||||
# Have stdin translate Latin-1 input into UTF-8 input
|
|
||||||
sys.stdin = EncodedFile(sys.stdin, 'utf-8', 'latin-1')
|
|
||||||
|
|||||||
103
Lib/codeop.py
vendored
103
Lib/codeop.py
vendored
@@ -10,30 +10,6 @@ and:
|
|||||||
syntax error (OverflowError and ValueError can be produced by
|
syntax error (OverflowError and ValueError can be produced by
|
||||||
malformed literals).
|
malformed literals).
|
||||||
|
|
||||||
Approach:
|
|
||||||
|
|
||||||
First, check if the source consists entirely of blank lines and
|
|
||||||
comments; if so, replace it with 'pass', because the built-in
|
|
||||||
parser doesn't always do the right thing for these.
|
|
||||||
|
|
||||||
Compile three times: as is, with \n, and with \n\n appended. If it
|
|
||||||
compiles as is, it's complete. If it compiles with one \n appended,
|
|
||||||
we expect more. If it doesn't compile either way, we compare the
|
|
||||||
error we get when compiling with \n or \n\n appended. If the errors
|
|
||||||
are the same, the code is broken. But if the errors are different, we
|
|
||||||
expect more. Not intuitive; not even guaranteed to hold in future
|
|
||||||
releases; but this matches the compiler's behavior from Python 1.4
|
|
||||||
through 2.2, at least.
|
|
||||||
|
|
||||||
Caveat:
|
|
||||||
|
|
||||||
It is possible (but not likely) that the parser stops parsing with a
|
|
||||||
successful outcome before reaching the end of the source; in this
|
|
||||||
case, trailing symbols may be ignored instead of causing an error.
|
|
||||||
For example, a backslash followed by two newlines may be followed by
|
|
||||||
arbitrary garbage. This will be fixed once the API for the parser is
|
|
||||||
better.
|
|
||||||
|
|
||||||
The two interfaces are:
|
The two interfaces are:
|
||||||
|
|
||||||
compile_command(source, filename, symbol):
|
compile_command(source, filename, symbol):
|
||||||
@@ -64,53 +40,53 @@ _features = [getattr(__future__, fname)
|
|||||||
|
|
||||||
__all__ = ["compile_command", "Compile", "CommandCompiler"]
|
__all__ = ["compile_command", "Compile", "CommandCompiler"]
|
||||||
|
|
||||||
PyCF_DONT_IMPLY_DEDENT = 0x200 # Matches pythonrun.h
|
# The following flags match the values from Include/cpython/compile.h
|
||||||
|
# Caveat emptor: These flags are undocumented on purpose and depending
|
||||||
|
# on their effect outside the standard library is **unsupported**.
|
||||||
|
PyCF_DONT_IMPLY_DEDENT = 0x200
|
||||||
|
PyCF_ALLOW_INCOMPLETE_INPUT = 0x4000
|
||||||
|
|
||||||
def _maybe_compile(compiler, source, filename, symbol):
|
def _maybe_compile(compiler, source, filename, symbol):
|
||||||
# Check for source consisting of only blank lines and comments
|
# Check for source consisting of only blank lines and comments.
|
||||||
for line in source.split("\n"):
|
for line in source.split("\n"):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line and line[0] != '#':
|
if line and line[0] != '#':
|
||||||
break # Leave it alone
|
break # Leave it alone.
|
||||||
else:
|
else:
|
||||||
if symbol != "eval":
|
if symbol != "eval":
|
||||||
source = "pass" # Replace it with a 'pass' statement
|
source = "pass" # Replace it with a 'pass' statement
|
||||||
|
|
||||||
err = err1 = err2 = None
|
# Disable compiler warnings when checking for incomplete input.
|
||||||
code = code1 = code2 = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
code = compiler(source, filename, symbol)
|
|
||||||
except SyntaxError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Catch syntax warnings after the first compile
|
|
||||||
# to emit warnings (SyntaxWarning, DeprecationWarning) at most once.
|
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("error")
|
warnings.simplefilter("ignore", (SyntaxWarning, DeprecationWarning))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
code1 = compiler(source + "\n", filename, symbol)
|
compiler(source, filename, symbol)
|
||||||
except SyntaxError as e:
|
except SyntaxError: # Let other compile() errors propagate.
|
||||||
err1 = e
|
try:
|
||||||
|
compiler(source + "\n", filename, symbol)
|
||||||
|
return None
|
||||||
|
except SyntaxError as e:
|
||||||
|
if "incomplete input" in str(e):
|
||||||
|
return None
|
||||||
|
# fallthrough
|
||||||
|
|
||||||
try:
|
return compiler(source, filename, symbol, incomplete_input=False)
|
||||||
code2 = compiler(source + "\n\n", filename, symbol)
|
|
||||||
except SyntaxError as e:
|
|
||||||
err2 = e
|
|
||||||
|
|
||||||
try:
|
def _is_syntax_error(err1, err2):
|
||||||
if code:
|
rep1 = repr(err1)
|
||||||
return code
|
rep2 = repr(err2)
|
||||||
if not code1 and repr(err1) == repr(err2):
|
if "was never closed" in rep1 and "was never closed" in rep2:
|
||||||
raise err1
|
return False
|
||||||
finally:
|
if rep1 == rep2:
|
||||||
err1 = err2 = None
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _compile(source, filename, symbol, incomplete_input=True):
|
||||||
def _compile(source, filename, symbol):
|
flags = 0
|
||||||
return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT)
|
if incomplete_input:
|
||||||
|
flags |= PyCF_ALLOW_INCOMPLETE_INPUT
|
||||||
|
flags |= PyCF_DONT_IMPLY_DEDENT
|
||||||
|
return compile(source, filename, symbol, flags)
|
||||||
|
|
||||||
|
|
||||||
def compile_command(source, filename="<input>", symbol="single"):
|
def compile_command(source, filename="<input>", symbol="single"):
|
||||||
@@ -134,24 +110,25 @@ def compile_command(source, filename="<input>", symbol="single"):
|
|||||||
"""
|
"""
|
||||||
return _maybe_compile(_compile, source, filename, symbol)
|
return _maybe_compile(_compile, source, filename, symbol)
|
||||||
|
|
||||||
|
|
||||||
class Compile:
|
class Compile:
|
||||||
"""Instances of this class behave much like the built-in compile
|
"""Instances of this class behave much like the built-in compile
|
||||||
function, but if one is used to compile text containing a future
|
function, but if one is used to compile text containing a future
|
||||||
statement, it "remembers" and compiles all subsequent program texts
|
statement, it "remembers" and compiles all subsequent program texts
|
||||||
with the statement in force."""
|
with the statement in force."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.flags = PyCF_DONT_IMPLY_DEDENT
|
self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT
|
||||||
|
|
||||||
def __call__(self, source, filename, symbol):
|
def __call__(self, source, filename, symbol, **kwargs):
|
||||||
codeob = compile(source, filename, symbol, self.flags, True)
|
flags = self.flags
|
||||||
|
if kwargs.get('incomplete_input', True) is False:
|
||||||
|
flags &= ~PyCF_DONT_IMPLY_DEDENT
|
||||||
|
flags &= ~PyCF_ALLOW_INCOMPLETE_INPUT
|
||||||
|
codeob = compile(source, filename, symbol, flags, True)
|
||||||
for feature in _features:
|
for feature in _features:
|
||||||
if codeob.co_flags & feature.compiler_flag:
|
if codeob.co_flags & feature.compiler_flag:
|
||||||
self.flags |= feature.compiler_flag
|
self.flags |= feature.compiler_flag
|
||||||
return codeob
|
return codeob
|
||||||
|
|
||||||
|
|
||||||
class CommandCompiler:
|
class CommandCompiler:
|
||||||
"""Instances of this class have __call__ methods identical in
|
"""Instances of this class have __call__ methods identical in
|
||||||
signature to compile_command; the difference is that if the
|
signature to compile_command; the difference is that if the
|
||||||
|
|||||||
159
Lib/collections/__init__.py
vendored
159
Lib/collections/__init__.py
vendored
@@ -45,6 +45,11 @@ except ImportError:
|
|||||||
else:
|
else:
|
||||||
_collections_abc.MutableSequence.register(deque)
|
_collections_abc.MutableSequence.register(deque)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from _collections import _deque_iterator
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from _collections import defaultdict
|
from _collections import defaultdict
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -94,17 +99,19 @@ class OrderedDict(dict):
|
|||||||
# Individual links are kept alive by the hard reference in self.__map.
|
# Individual links are kept alive by the hard reference in self.__map.
|
||||||
# Those hard references disappear when a key is deleted from an OrderedDict.
|
# Those hard references disappear when a key is deleted from an OrderedDict.
|
||||||
|
|
||||||
|
def __new__(cls, /, *args, **kwds):
|
||||||
|
"Create the ordered dict object and set up the underlying structures."
|
||||||
|
self = dict.__new__(cls)
|
||||||
|
self.__hardroot = _Link()
|
||||||
|
self.__root = root = _proxy(self.__hardroot)
|
||||||
|
root.prev = root.next = root
|
||||||
|
self.__map = {}
|
||||||
|
return self
|
||||||
|
|
||||||
def __init__(self, other=(), /, **kwds):
|
def __init__(self, other=(), /, **kwds):
|
||||||
'''Initialize an ordered dictionary. The signature is the same as
|
'''Initialize an ordered dictionary. The signature is the same as
|
||||||
regular dictionaries. Keyword argument order is preserved.
|
regular dictionaries. Keyword argument order is preserved.
|
||||||
'''
|
'''
|
||||||
try:
|
|
||||||
self.__root
|
|
||||||
except AttributeError:
|
|
||||||
self.__hardroot = _Link()
|
|
||||||
self.__root = root = _proxy(self.__hardroot)
|
|
||||||
root.prev = root.next = root
|
|
||||||
self.__map = {}
|
|
||||||
self.__update(other, **kwds)
|
self.__update(other, **kwds)
|
||||||
|
|
||||||
def __setitem__(self, key, value,
|
def __setitem__(self, key, value,
|
||||||
@@ -240,11 +247,19 @@ class OrderedDict(dict):
|
|||||||
is raised.
|
is raised.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if key in self:
|
marker = self.__marker
|
||||||
result = self[key]
|
result = dict.pop(self, key, marker)
|
||||||
del self[key]
|
if result is not marker:
|
||||||
|
# The same as in __delitem__().
|
||||||
|
link = self.__map.pop(key)
|
||||||
|
link_prev = link.prev
|
||||||
|
link_next = link.next
|
||||||
|
link_prev.next = link_next
|
||||||
|
link_next.prev = link_prev
|
||||||
|
link.prev = None
|
||||||
|
link.next = None
|
||||||
return result
|
return result
|
||||||
if default is self.__marker:
|
if default is marker:
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
return default
|
return default
|
||||||
|
|
||||||
@@ -263,14 +278,26 @@ class OrderedDict(dict):
|
|||||||
'od.__repr__() <==> repr(od)'
|
'od.__repr__() <==> repr(od)'
|
||||||
if not self:
|
if not self:
|
||||||
return '%s()' % (self.__class__.__name__,)
|
return '%s()' % (self.__class__.__name__,)
|
||||||
return '%s(%r)' % (self.__class__.__name__, list(self.items()))
|
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
'Return state information for pickling'
|
'Return state information for pickling'
|
||||||
inst_dict = vars(self).copy()
|
state = self.__getstate__()
|
||||||
for k in vars(OrderedDict()):
|
if state:
|
||||||
inst_dict.pop(k, None)
|
if isinstance(state, tuple):
|
||||||
return self.__class__, (), inst_dict or None, None, iter(self.items())
|
state, slots = state
|
||||||
|
else:
|
||||||
|
slots = {}
|
||||||
|
state = state.copy()
|
||||||
|
slots = slots.copy()
|
||||||
|
for k in vars(OrderedDict()):
|
||||||
|
state.pop(k, None)
|
||||||
|
slots.pop(k, None)
|
||||||
|
if slots:
|
||||||
|
state = state, slots
|
||||||
|
else:
|
||||||
|
state = state or None
|
||||||
|
return self.__class__, (), state, None, iter(self.items())
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
'od.copy() -> a shallow copy of od'
|
'od.copy() -> a shallow copy of od'
|
||||||
@@ -491,9 +518,12 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
|
|||||||
# specified a particular module.
|
# specified a particular module.
|
||||||
if module is None:
|
if module is None:
|
||||||
try:
|
try:
|
||||||
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
module = _sys._getframemodulename(1) or '__main__'
|
||||||
except (AttributeError, ValueError):
|
except AttributeError:
|
||||||
pass
|
try:
|
||||||
|
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
if module is not None:
|
if module is not None:
|
||||||
result.__module__ = module
|
result.__module__ = module
|
||||||
|
|
||||||
@@ -613,11 +643,9 @@ class Counter(dict):
|
|||||||
['A', 'A', 'B', 'B', 'C', 'C']
|
['A', 'A', 'B', 'B', 'C', 'C']
|
||||||
|
|
||||||
# Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
|
# Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1
|
||||||
|
>>> import math
|
||||||
>>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
|
>>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
|
||||||
>>> product = 1
|
>>> math.prod(prime_factors.elements())
|
||||||
>>> for factor in prime_factors.elements(): # loop over factors
|
|
||||||
... product *= factor # and multiply them
|
|
||||||
>>> product
|
|
||||||
1836
|
1836
|
||||||
|
|
||||||
Note, if an element's count has been set to zero or is a negative
|
Note, if an element's count has been set to zero or is a negative
|
||||||
@@ -714,42 +742,6 @@ class Counter(dict):
|
|||||||
if elem in self:
|
if elem in self:
|
||||||
super().__delitem__(elem)
|
super().__delitem__(elem)
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
'True if all counts agree. Missing counts are treated as zero.'
|
|
||||||
if not isinstance(other, Counter):
|
|
||||||
return NotImplemented
|
|
||||||
return all(self[e] == other[e] for c in (self, other) for e in c)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
'True if any counts disagree. Missing counts are treated as zero.'
|
|
||||||
if not isinstance(other, Counter):
|
|
||||||
return NotImplemented
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
'True if all counts in self are a subset of those in other.'
|
|
||||||
if not isinstance(other, Counter):
|
|
||||||
return NotImplemented
|
|
||||||
return all(self[e] <= other[e] for c in (self, other) for e in c)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
'True if all counts in self are a proper subset of those in other.'
|
|
||||||
if not isinstance(other, Counter):
|
|
||||||
return NotImplemented
|
|
||||||
return self <= other and self != other
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
'True if all counts in self are a superset of those in other.'
|
|
||||||
if not isinstance(other, Counter):
|
|
||||||
return NotImplemented
|
|
||||||
return all(self[e] >= other[e] for c in (self, other) for e in c)
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
'True if all counts in self are a proper superset of those in other.'
|
|
||||||
if not isinstance(other, Counter):
|
|
||||||
return NotImplemented
|
|
||||||
return self >= other and self != other
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if not self:
|
if not self:
|
||||||
return f'{self.__class__.__name__}()'
|
return f'{self.__class__.__name__}()'
|
||||||
@@ -795,6 +787,42 @@ class Counter(dict):
|
|||||||
# (cp >= cq) == (sp >= sq)
|
# (cp >= cq) == (sp >= sq)
|
||||||
# (cp > cq) == (sp > sq)
|
# (cp > cq) == (sp > sq)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
'True if all counts agree. Missing counts are treated as zero.'
|
||||||
|
if not isinstance(other, Counter):
|
||||||
|
return NotImplemented
|
||||||
|
return all(self[e] == other[e] for c in (self, other) for e in c)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
'True if any counts disagree. Missing counts are treated as zero.'
|
||||||
|
if not isinstance(other, Counter):
|
||||||
|
return NotImplemented
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
'True if all counts in self are a subset of those in other.'
|
||||||
|
if not isinstance(other, Counter):
|
||||||
|
return NotImplemented
|
||||||
|
return all(self[e] <= other[e] for c in (self, other) for e in c)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
'True if all counts in self are a proper subset of those in other.'
|
||||||
|
if not isinstance(other, Counter):
|
||||||
|
return NotImplemented
|
||||||
|
return self <= other and self != other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
'True if all counts in self are a superset of those in other.'
|
||||||
|
if not isinstance(other, Counter):
|
||||||
|
return NotImplemented
|
||||||
|
return all(self[e] >= other[e] for c in (self, other) for e in c)
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
'True if all counts in self are a proper superset of those in other.'
|
||||||
|
if not isinstance(other, Counter):
|
||||||
|
return NotImplemented
|
||||||
|
return self >= other and self != other
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
'''Add counts from two counters.
|
'''Add counts from two counters.
|
||||||
|
|
||||||
@@ -997,8 +1025,8 @@ class ChainMap(_collections_abc.MutableMapping):
|
|||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
d = {}
|
d = {}
|
||||||
for mapping in reversed(self.maps):
|
for mapping in map(dict.fromkeys, reversed(self.maps)):
|
||||||
d.update(dict.fromkeys(mapping)) # reuses stored hash values if possible
|
d |= mapping # reuses stored hash values if possible
|
||||||
return iter(d)
|
return iter(d)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
@@ -1118,10 +1146,17 @@ class UserDict(_collections_abc.MutableMapping):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self.data)
|
return iter(self.data)
|
||||||
|
|
||||||
# Modify __contains__ to work correctly when __missing__ is present
|
# Modify __contains__ and get() to work like dict
|
||||||
|
# does when __missing__ is present.
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
return key in self.data
|
return key in self.data
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
if key in self:
|
||||||
|
return self[key]
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
# Now, add the methods in dicts but not in MutableMapping
|
# Now, add the methods in dicts but not in MutableMapping
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.data)
|
return repr(self.data)
|
||||||
|
|||||||
28
Lib/colorsys.py
vendored
28
Lib/colorsys.py
vendored
@@ -1,10 +1,14 @@
|
|||||||
"""Conversion functions between RGB and other color systems.
|
"""Conversion functions between RGB and other color systems.
|
||||||
|
|
||||||
This modules provides two functions for each color system ABC:
|
This modules provides two functions for each color system ABC:
|
||||||
|
|
||||||
rgb_to_abc(r, g, b) --> a, b, c
|
rgb_to_abc(r, g, b) --> a, b, c
|
||||||
abc_to_rgb(a, b, c) --> r, g, b
|
abc_to_rgb(a, b, c) --> r, g, b
|
||||||
|
|
||||||
All inputs and outputs are triples of floats in the range [0.0...1.0]
|
All inputs and outputs are triples of floats in the range [0.0...1.0]
|
||||||
(with the exception of I and Q, which covers a slightly larger range).
|
(with the exception of I and Q, which covers a slightly larger range).
|
||||||
Inputs outside the valid range may cause exceptions or invalid outputs.
|
Inputs outside the valid range may cause exceptions or invalid outputs.
|
||||||
|
|
||||||
Supported color systems:
|
Supported color systems:
|
||||||
RGB: Red, Green, Blue components
|
RGB: Red, Green, Blue components
|
||||||
YIQ: Luminance, Chrominance (used by composite video signals)
|
YIQ: Luminance, Chrominance (used by composite video signals)
|
||||||
@@ -71,17 +75,18 @@ def yiq_to_rgb(y, i, q):
|
|||||||
def rgb_to_hls(r, g, b):
|
def rgb_to_hls(r, g, b):
|
||||||
maxc = max(r, g, b)
|
maxc = max(r, g, b)
|
||||||
minc = min(r, g, b)
|
minc = min(r, g, b)
|
||||||
# XXX Can optimize (maxc+minc) and (maxc-minc)
|
sumc = (maxc+minc)
|
||||||
l = (minc+maxc)/2.0
|
rangec = (maxc-minc)
|
||||||
|
l = sumc/2.0
|
||||||
if minc == maxc:
|
if minc == maxc:
|
||||||
return 0.0, l, 0.0
|
return 0.0, l, 0.0
|
||||||
if l <= 0.5:
|
if l <= 0.5:
|
||||||
s = (maxc-minc) / (maxc+minc)
|
s = rangec / sumc
|
||||||
else:
|
else:
|
||||||
s = (maxc-minc) / (2.0-maxc-minc)
|
s = rangec / (2.0-maxc-minc) # Not always 2.0-sumc: gh-106498.
|
||||||
rc = (maxc-r) / (maxc-minc)
|
rc = (maxc-r) / rangec
|
||||||
gc = (maxc-g) / (maxc-minc)
|
gc = (maxc-g) / rangec
|
||||||
bc = (maxc-b) / (maxc-minc)
|
bc = (maxc-b) / rangec
|
||||||
if r == maxc:
|
if r == maxc:
|
||||||
h = bc-gc
|
h = bc-gc
|
||||||
elif g == maxc:
|
elif g == maxc:
|
||||||
@@ -120,13 +125,14 @@ def _v(m1, m2, hue):
|
|||||||
def rgb_to_hsv(r, g, b):
|
def rgb_to_hsv(r, g, b):
|
||||||
maxc = max(r, g, b)
|
maxc = max(r, g, b)
|
||||||
minc = min(r, g, b)
|
minc = min(r, g, b)
|
||||||
|
rangec = (maxc-minc)
|
||||||
v = maxc
|
v = maxc
|
||||||
if minc == maxc:
|
if minc == maxc:
|
||||||
return 0.0, 0.0, v
|
return 0.0, 0.0, v
|
||||||
s = (maxc-minc) / maxc
|
s = rangec / maxc
|
||||||
rc = (maxc-r) / (maxc-minc)
|
rc = (maxc-r) / rangec
|
||||||
gc = (maxc-g) / (maxc-minc)
|
gc = (maxc-g) / rangec
|
||||||
bc = (maxc-b) / (maxc-minc)
|
bc = (maxc-b) / rangec
|
||||||
if r == maxc:
|
if r == maxc:
|
||||||
h = bc-gc
|
h = bc-gc
|
||||||
elif g == maxc:
|
elif g == maxc:
|
||||||
|
|||||||
286
Lib/compileall.py
vendored
286
Lib/compileall.py
vendored
@@ -4,7 +4,7 @@ When called as a script with arguments, this compiles the directories
|
|||||||
given as arguments recursively; the -l option prevents it from
|
given as arguments recursively; the -l option prevents it from
|
||||||
recursing into directories.
|
recursing into directories.
|
||||||
|
|
||||||
Without arguments, if compiles all modules on sys.path, without
|
Without arguments, it compiles all modules on sys.path, without
|
||||||
recursing into subdirectories. (Even though it should do so for
|
recursing into subdirectories. (Even though it should do so for
|
||||||
packages -- for now, you'll have to deal with packages separately.)
|
packages -- for now, you'll have to deal with packages separately.)
|
||||||
|
|
||||||
@@ -15,16 +15,14 @@ import sys
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import py_compile
|
import py_compile
|
||||||
import struct
|
import struct
|
||||||
|
import filecmp
|
||||||
|
|
||||||
try:
|
|
||||||
from concurrent.futures import ProcessPoolExecutor
|
|
||||||
except ImportError:
|
|
||||||
ProcessPoolExecutor = None
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
__all__ = ["compile_dir","compile_file","compile_path"]
|
__all__ = ["compile_dir","compile_file","compile_path"]
|
||||||
|
|
||||||
def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
|
def _walk_dir(dir, maxlevels, quiet=0):
|
||||||
if quiet < 2 and isinstance(dir, os.PathLike):
|
if quiet < 2 and isinstance(dir, os.PathLike):
|
||||||
dir = os.fspath(dir)
|
dir = os.fspath(dir)
|
||||||
if not quiet:
|
if not quiet:
|
||||||
@@ -40,59 +38,94 @@ def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
|
|||||||
if name == '__pycache__':
|
if name == '__pycache__':
|
||||||
continue
|
continue
|
||||||
fullname = os.path.join(dir, name)
|
fullname = os.path.join(dir, name)
|
||||||
if ddir is not None:
|
|
||||||
dfile = os.path.join(ddir, name)
|
|
||||||
else:
|
|
||||||
dfile = None
|
|
||||||
if not os.path.isdir(fullname):
|
if not os.path.isdir(fullname):
|
||||||
yield fullname
|
yield fullname
|
||||||
elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
|
elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
|
||||||
os.path.isdir(fullname) and not os.path.islink(fullname)):
|
os.path.isdir(fullname) and not os.path.islink(fullname)):
|
||||||
yield from _walk_dir(fullname, ddir=dfile,
|
yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
|
||||||
maxlevels=maxlevels - 1, quiet=quiet)
|
quiet=quiet)
|
||||||
|
|
||||||
def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
|
def compile_dir(dir, maxlevels=None, ddir=None, force=False,
|
||||||
quiet=0, legacy=False, optimize=-1, workers=1):
|
rx=None, quiet=0, legacy=False, optimize=-1, workers=1,
|
||||||
|
invalidation_mode=None, *, stripdir=None,
|
||||||
|
prependdir=None, limit_sl_dest=None, hardlink_dupes=False):
|
||||||
"""Byte-compile all modules in the given directory tree.
|
"""Byte-compile all modules in the given directory tree.
|
||||||
|
|
||||||
Arguments (only dir is required):
|
Arguments (only dir is required):
|
||||||
|
|
||||||
dir: the directory to byte-compile
|
dir: the directory to byte-compile
|
||||||
maxlevels: maximum recursion level (default 10)
|
maxlevels: maximum recursion level (default `sys.getrecursionlimit()`)
|
||||||
ddir: the directory that will be prepended to the path to the
|
ddir: the directory that will be prepended to the path to the
|
||||||
file as it is compiled into each byte-code file.
|
file as it is compiled into each byte-code file.
|
||||||
force: if True, force compilation, even if timestamps are up-to-date
|
force: if True, force compilation, even if timestamps are up-to-date
|
||||||
quiet: full output with False or 0, errors only with 1,
|
quiet: full output with False or 0, errors only with 1,
|
||||||
no output with 2
|
no output with 2
|
||||||
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
|
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
|
||||||
optimize: optimization level or -1 for level of the interpreter
|
optimize: int or list of optimization levels or -1 for level of
|
||||||
|
the interpreter. Multiple levels leads to multiple compiled
|
||||||
|
files each with one optimization level.
|
||||||
workers: maximum number of parallel workers
|
workers: maximum number of parallel workers
|
||||||
|
invalidation_mode: how the up-to-dateness of the pyc will be checked
|
||||||
|
stripdir: part of path to left-strip from source file path
|
||||||
|
prependdir: path to prepend to beginning of original file path, applied
|
||||||
|
after stripdir
|
||||||
|
limit_sl_dest: ignore symlinks if they are pointing outside of
|
||||||
|
the defined path
|
||||||
|
hardlink_dupes: hardlink duplicated pyc files
|
||||||
"""
|
"""
|
||||||
if workers is not None and workers < 0:
|
ProcessPoolExecutor = None
|
||||||
|
if ddir is not None and (stripdir is not None or prependdir is not None):
|
||||||
|
raise ValueError(("Destination dir (ddir) cannot be used "
|
||||||
|
"in combination with stripdir or prependdir"))
|
||||||
|
if ddir is not None:
|
||||||
|
stripdir = dir
|
||||||
|
prependdir = ddir
|
||||||
|
ddir = None
|
||||||
|
if workers < 0:
|
||||||
raise ValueError('workers must be greater or equal to 0')
|
raise ValueError('workers must be greater or equal to 0')
|
||||||
|
if workers != 1:
|
||||||
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels,
|
# Check if this is a system where ProcessPoolExecutor can function.
|
||||||
ddir=ddir)
|
from concurrent.futures.process import _check_system_limits
|
||||||
|
try:
|
||||||
|
_check_system_limits()
|
||||||
|
except NotImplementedError:
|
||||||
|
workers = 1
|
||||||
|
else:
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
if maxlevels is None:
|
||||||
|
maxlevels = sys.getrecursionlimit()
|
||||||
|
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
|
||||||
success = True
|
success = True
|
||||||
if workers is not None and workers != 1 and ProcessPoolExecutor is not None:
|
if workers != 1 and ProcessPoolExecutor is not None:
|
||||||
|
# If workers == 0, let ProcessPoolExecutor choose
|
||||||
workers = workers or None
|
workers = workers or None
|
||||||
with ProcessPoolExecutor(max_workers=workers) as executor:
|
with ProcessPoolExecutor(max_workers=workers) as executor:
|
||||||
results = executor.map(partial(compile_file,
|
results = executor.map(partial(compile_file,
|
||||||
ddir=ddir, force=force,
|
ddir=ddir, force=force,
|
||||||
rx=rx, quiet=quiet,
|
rx=rx, quiet=quiet,
|
||||||
legacy=legacy,
|
legacy=legacy,
|
||||||
optimize=optimize),
|
optimize=optimize,
|
||||||
|
invalidation_mode=invalidation_mode,
|
||||||
|
stripdir=stripdir,
|
||||||
|
prependdir=prependdir,
|
||||||
|
limit_sl_dest=limit_sl_dest,
|
||||||
|
hardlink_dupes=hardlink_dupes),
|
||||||
files)
|
files)
|
||||||
success = min(results, default=True)
|
success = min(results, default=True)
|
||||||
else:
|
else:
|
||||||
for file in files:
|
for file in files:
|
||||||
if not compile_file(file, ddir, force, rx, quiet,
|
if not compile_file(file, ddir, force, rx, quiet,
|
||||||
legacy, optimize):
|
legacy, optimize, invalidation_mode,
|
||||||
|
stripdir=stripdir, prependdir=prependdir,
|
||||||
|
limit_sl_dest=limit_sl_dest,
|
||||||
|
hardlink_dupes=hardlink_dupes):
|
||||||
success = False
|
success = False
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
||||||
legacy=False, optimize=-1):
|
legacy=False, optimize=-1,
|
||||||
|
invalidation_mode=None, *, stripdir=None, prependdir=None,
|
||||||
|
limit_sl_dest=None, hardlink_dupes=False):
|
||||||
"""Byte-compile one file.
|
"""Byte-compile one file.
|
||||||
|
|
||||||
Arguments (only fullname is required):
|
Arguments (only fullname is required):
|
||||||
@@ -104,49 +137,114 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
|||||||
quiet: full output with False or 0, errors only with 1,
|
quiet: full output with False or 0, errors only with 1,
|
||||||
no output with 2
|
no output with 2
|
||||||
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
|
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
|
||||||
optimize: optimization level or -1 for level of the interpreter
|
optimize: int or list of optimization levels or -1 for level of
|
||||||
|
the interpreter. Multiple levels leads to multiple compiled
|
||||||
|
files each with one optimization level.
|
||||||
|
invalidation_mode: how the up-to-dateness of the pyc will be checked
|
||||||
|
stripdir: part of path to left-strip from source file path
|
||||||
|
prependdir: path to prepend to beginning of original file path, applied
|
||||||
|
after stripdir
|
||||||
|
limit_sl_dest: ignore symlinks if they are pointing outside of
|
||||||
|
the defined path.
|
||||||
|
hardlink_dupes: hardlink duplicated pyc files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if ddir is not None and (stripdir is not None or prependdir is not None):
|
||||||
|
raise ValueError(("Destination dir (ddir) cannot be used "
|
||||||
|
"in combination with stripdir or prependdir"))
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
if quiet < 2 and isinstance(fullname, os.PathLike):
|
fullname = os.fspath(fullname)
|
||||||
fullname = os.fspath(fullname)
|
stripdir = os.fspath(stripdir) if stripdir is not None else None
|
||||||
name = os.path.basename(fullname)
|
name = os.path.basename(fullname)
|
||||||
|
|
||||||
|
dfile = None
|
||||||
|
|
||||||
if ddir is not None:
|
if ddir is not None:
|
||||||
dfile = os.path.join(ddir, name)
|
dfile = os.path.join(ddir, name)
|
||||||
else:
|
|
||||||
dfile = None
|
if stripdir is not None:
|
||||||
|
fullname_parts = fullname.split(os.path.sep)
|
||||||
|
stripdir_parts = stripdir.split(os.path.sep)
|
||||||
|
ddir_parts = list(fullname_parts)
|
||||||
|
|
||||||
|
for spart, opart in zip(stripdir_parts, fullname_parts):
|
||||||
|
if spart == opart:
|
||||||
|
ddir_parts.remove(spart)
|
||||||
|
|
||||||
|
dfile = os.path.join(*ddir_parts)
|
||||||
|
|
||||||
|
if prependdir is not None:
|
||||||
|
if dfile is None:
|
||||||
|
dfile = os.path.join(prependdir, fullname)
|
||||||
|
else:
|
||||||
|
dfile = os.path.join(prependdir, dfile)
|
||||||
|
|
||||||
|
if isinstance(optimize, int):
|
||||||
|
optimize = [optimize]
|
||||||
|
|
||||||
|
# Use set() to remove duplicates.
|
||||||
|
# Use sorted() to create pyc files in a deterministic order.
|
||||||
|
optimize = sorted(set(optimize))
|
||||||
|
|
||||||
|
if hardlink_dupes and len(optimize) < 2:
|
||||||
|
raise ValueError("Hardlinking of duplicated bytecode makes sense "
|
||||||
|
"only for more than one optimization level")
|
||||||
|
|
||||||
if rx is not None:
|
if rx is not None:
|
||||||
mo = rx.search(fullname)
|
mo = rx.search(fullname)
|
||||||
if mo:
|
if mo:
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
if limit_sl_dest is not None and os.path.islink(fullname):
|
||||||
|
if Path(limit_sl_dest).resolve() not in Path(fullname).resolve().parents:
|
||||||
|
return success
|
||||||
|
|
||||||
|
opt_cfiles = {}
|
||||||
|
|
||||||
if os.path.isfile(fullname):
|
if os.path.isfile(fullname):
|
||||||
if legacy:
|
for opt_level in optimize:
|
||||||
cfile = fullname + 'c'
|
if legacy:
|
||||||
else:
|
opt_cfiles[opt_level] = fullname + 'c'
|
||||||
if optimize >= 0:
|
|
||||||
opt = optimize if optimize >= 1 else ''
|
|
||||||
cfile = importlib.util.cache_from_source(
|
|
||||||
fullname, optimization=opt)
|
|
||||||
else:
|
else:
|
||||||
cfile = importlib.util.cache_from_source(fullname)
|
if opt_level >= 0:
|
||||||
cache_dir = os.path.dirname(cfile)
|
opt = opt_level if opt_level >= 1 else ''
|
||||||
|
cfile = (importlib.util.cache_from_source(
|
||||||
|
fullname, optimization=opt))
|
||||||
|
opt_cfiles[opt_level] = cfile
|
||||||
|
else:
|
||||||
|
cfile = importlib.util.cache_from_source(fullname)
|
||||||
|
opt_cfiles[opt_level] = cfile
|
||||||
|
|
||||||
head, tail = name[:-3], name[-3:]
|
head, tail = name[:-3], name[-3:]
|
||||||
if tail == '.py':
|
if tail == '.py':
|
||||||
if not force:
|
if not force:
|
||||||
try:
|
try:
|
||||||
mtime = int(os.stat(fullname).st_mtime)
|
mtime = int(os.stat(fullname).st_mtime)
|
||||||
expect = struct.pack('<4sl', importlib.util.MAGIC_NUMBER,
|
expect = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
|
||||||
mtime)
|
0, mtime & 0xFFFF_FFFF)
|
||||||
with open(cfile, 'rb') as chandle:
|
for cfile in opt_cfiles.values():
|
||||||
actual = chandle.read(8)
|
with open(cfile, 'rb') as chandle:
|
||||||
if expect == actual:
|
actual = chandle.read(12)
|
||||||
|
if expect != actual:
|
||||||
|
break
|
||||||
|
else:
|
||||||
return success
|
return success
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print('Compiling {!r}...'.format(fullname))
|
print('Compiling {!r}...'.format(fullname))
|
||||||
try:
|
try:
|
||||||
ok = py_compile.compile(fullname, cfile, dfile, True,
|
for index, opt_level in enumerate(optimize):
|
||||||
optimize=optimize)
|
cfile = opt_cfiles[opt_level]
|
||||||
|
ok = py_compile.compile(fullname, cfile, dfile, True,
|
||||||
|
optimize=opt_level,
|
||||||
|
invalidation_mode=invalidation_mode)
|
||||||
|
if index > 0 and hardlink_dupes:
|
||||||
|
previous_cfile = opt_cfiles[optimize[index - 1]]
|
||||||
|
if filecmp.cmp(cfile, previous_cfile, shallow=False):
|
||||||
|
os.unlink(cfile)
|
||||||
|
os.link(previous_cfile, cfile)
|
||||||
except py_compile.PyCompileError as err:
|
except py_compile.PyCompileError as err:
|
||||||
success = False
|
success = False
|
||||||
if quiet >= 2:
|
if quiet >= 2:
|
||||||
@@ -156,9 +254,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
|||||||
else:
|
else:
|
||||||
print('*** ', end='')
|
print('*** ', end='')
|
||||||
# escape non-printable characters in msg
|
# escape non-printable characters in msg
|
||||||
msg = err.msg.encode(sys.stdout.encoding,
|
encoding = sys.stdout.encoding or sys.getdefaultencoding()
|
||||||
errors='backslashreplace')
|
msg = err.msg.encode(encoding, errors='backslashreplace').decode(encoding)
|
||||||
msg = msg.decode(sys.stdout.encoding)
|
|
||||||
print(msg)
|
print(msg)
|
||||||
except (SyntaxError, UnicodeError, OSError) as e:
|
except (SyntaxError, UnicodeError, OSError) as e:
|
||||||
success = False
|
success = False
|
||||||
@@ -175,7 +272,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
|||||||
return success
|
return success
|
||||||
|
|
||||||
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
|
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
|
||||||
legacy=False, optimize=-1):
|
legacy=False, optimize=-1,
|
||||||
|
invalidation_mode=None):
|
||||||
"""Byte-compile all module on sys.path.
|
"""Byte-compile all module on sys.path.
|
||||||
|
|
||||||
Arguments (all optional):
|
Arguments (all optional):
|
||||||
@@ -186,6 +284,7 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
|
|||||||
quiet: as for compile_dir() (default 0)
|
quiet: as for compile_dir() (default 0)
|
||||||
legacy: as for compile_dir() (default False)
|
legacy: as for compile_dir() (default False)
|
||||||
optimize: as for compile_dir() (default -1)
|
optimize: as for compile_dir() (default -1)
|
||||||
|
invalidation_mode: as for compiler_dir()
|
||||||
"""
|
"""
|
||||||
success = True
|
success = True
|
||||||
for dir in sys.path:
|
for dir in sys.path:
|
||||||
@@ -193,9 +292,16 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
|
|||||||
if quiet < 2:
|
if quiet < 2:
|
||||||
print('Skipping current directory')
|
print('Skipping current directory')
|
||||||
else:
|
else:
|
||||||
success = success and compile_dir(dir, maxlevels, None,
|
success = success and compile_dir(
|
||||||
force, quiet=quiet,
|
dir,
|
||||||
legacy=legacy, optimize=optimize)
|
maxlevels,
|
||||||
|
None,
|
||||||
|
force,
|
||||||
|
quiet=quiet,
|
||||||
|
legacy=legacy,
|
||||||
|
optimize=optimize,
|
||||||
|
invalidation_mode=invalidation_mode,
|
||||||
|
)
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
|
||||||
@@ -206,7 +312,7 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Utilities to support installing Python libraries.')
|
description='Utilities to support installing Python libraries.')
|
||||||
parser.add_argument('-l', action='store_const', const=0,
|
parser.add_argument('-l', action='store_const', const=0,
|
||||||
default=10, dest='maxlevels',
|
default=None, dest='maxlevels',
|
||||||
help="don't recurse into subdirectories")
|
help="don't recurse into subdirectories")
|
||||||
parser.add_argument('-r', type=int, dest='recursion',
|
parser.add_argument('-r', type=int, dest='recursion',
|
||||||
help=('control the maximum recursion level. '
|
help=('control the maximum recursion level. '
|
||||||
@@ -224,6 +330,20 @@ def main():
|
|||||||
'compile-time tracebacks and in runtime '
|
'compile-time tracebacks and in runtime '
|
||||||
'tracebacks in cases where the source file is '
|
'tracebacks in cases where the source file is '
|
||||||
'unavailable'))
|
'unavailable'))
|
||||||
|
parser.add_argument('-s', metavar='STRIPDIR', dest='stripdir',
|
||||||
|
default=None,
|
||||||
|
help=('part of path to left-strip from path '
|
||||||
|
'to source file - for example buildroot. '
|
||||||
|
'`-d` and `-s` options cannot be '
|
||||||
|
'specified together.'))
|
||||||
|
parser.add_argument('-p', metavar='PREPENDDIR', dest='prependdir',
|
||||||
|
default=None,
|
||||||
|
help=('path to add as prefix to path '
|
||||||
|
'to source file - for example / to make '
|
||||||
|
'it absolute when some part is removed '
|
||||||
|
'by `-s` option. '
|
||||||
|
'`-d` and `-p` options cannot be '
|
||||||
|
'specified together.'))
|
||||||
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
|
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
|
||||||
help=('skip files matching the regular expression; '
|
help=('skip files matching the regular expression; '
|
||||||
'the regexp is searched for in the full path '
|
'the regexp is searched for in the full path '
|
||||||
@@ -238,6 +358,23 @@ def main():
|
|||||||
'to the equivalent of -l sys.path'))
|
'to the equivalent of -l sys.path'))
|
||||||
parser.add_argument('-j', '--workers', default=1,
|
parser.add_argument('-j', '--workers', default=1,
|
||||||
type=int, help='Run compileall concurrently')
|
type=int, help='Run compileall concurrently')
|
||||||
|
invalidation_modes = [mode.name.lower().replace('_', '-')
|
||||||
|
for mode in py_compile.PycInvalidationMode]
|
||||||
|
parser.add_argument('--invalidation-mode',
|
||||||
|
choices=sorted(invalidation_modes),
|
||||||
|
help=('set .pyc invalidation mode; defaults to '
|
||||||
|
'"checked-hash" if the SOURCE_DATE_EPOCH '
|
||||||
|
'environment variable is set, and '
|
||||||
|
'"timestamp" otherwise.'))
|
||||||
|
parser.add_argument('-o', action='append', type=int, dest='opt_levels',
|
||||||
|
help=('Optimization levels to run compilation with. '
|
||||||
|
'Default is -1 which uses the optimization level '
|
||||||
|
'of the Python interpreter itself (see -O).'))
|
||||||
|
parser.add_argument('-e', metavar='DIR', dest='limit_sl_dest',
|
||||||
|
help='Ignore symlinks pointing outsite of the DIR')
|
||||||
|
parser.add_argument('--hardlink-dupes', action='store_true',
|
||||||
|
dest='hardlink_dupes',
|
||||||
|
help='Hardlink duplicated pyc files')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
compile_dests = args.compile_dest
|
compile_dests = args.compile_dest
|
||||||
@@ -246,16 +383,31 @@ def main():
|
|||||||
import re
|
import re
|
||||||
args.rx = re.compile(args.rx)
|
args.rx = re.compile(args.rx)
|
||||||
|
|
||||||
|
if args.limit_sl_dest == "":
|
||||||
|
args.limit_sl_dest = None
|
||||||
|
|
||||||
if args.recursion is not None:
|
if args.recursion is not None:
|
||||||
maxlevels = args.recursion
|
maxlevels = args.recursion
|
||||||
else:
|
else:
|
||||||
maxlevels = args.maxlevels
|
maxlevels = args.maxlevels
|
||||||
|
|
||||||
|
if args.opt_levels is None:
|
||||||
|
args.opt_levels = [-1]
|
||||||
|
|
||||||
|
if len(args.opt_levels) == 1 and args.hardlink_dupes:
|
||||||
|
parser.error(("Hardlinking of duplicated bytecode makes sense "
|
||||||
|
"only for more than one optimization level."))
|
||||||
|
|
||||||
|
if args.ddir is not None and (
|
||||||
|
args.stripdir is not None or args.prependdir is not None
|
||||||
|
):
|
||||||
|
parser.error("-d cannot be used in combination with -s or -p")
|
||||||
|
|
||||||
# if flist is provided then load it
|
# if flist is provided then load it
|
||||||
if args.flist:
|
if args.flist:
|
||||||
try:
|
try:
|
||||||
with (sys.stdin if args.flist=='-' else open(args.flist)) as f:
|
with (sys.stdin if args.flist=='-' else
|
||||||
|
open(args.flist, encoding="utf-8")) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
compile_dests.append(line.strip())
|
compile_dests.append(line.strip())
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -263,8 +415,11 @@ def main():
|
|||||||
print("Error reading file list {}".format(args.flist))
|
print("Error reading file list {}".format(args.flist))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if args.workers is not None:
|
if args.invalidation_mode:
|
||||||
args.workers = args.workers or None
|
ivl_mode = args.invalidation_mode.replace('-', '_').upper()
|
||||||
|
invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
|
||||||
|
else:
|
||||||
|
invalidation_mode = None
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
try:
|
try:
|
||||||
@@ -272,17 +427,30 @@ def main():
|
|||||||
for dest in compile_dests:
|
for dest in compile_dests:
|
||||||
if os.path.isfile(dest):
|
if os.path.isfile(dest):
|
||||||
if not compile_file(dest, args.ddir, args.force, args.rx,
|
if not compile_file(dest, args.ddir, args.force, args.rx,
|
||||||
args.quiet, args.legacy):
|
args.quiet, args.legacy,
|
||||||
|
invalidation_mode=invalidation_mode,
|
||||||
|
stripdir=args.stripdir,
|
||||||
|
prependdir=args.prependdir,
|
||||||
|
optimize=args.opt_levels,
|
||||||
|
limit_sl_dest=args.limit_sl_dest,
|
||||||
|
hardlink_dupes=args.hardlink_dupes):
|
||||||
success = False
|
success = False
|
||||||
else:
|
else:
|
||||||
if not compile_dir(dest, maxlevels, args.ddir,
|
if not compile_dir(dest, maxlevels, args.ddir,
|
||||||
args.force, args.rx, args.quiet,
|
args.force, args.rx, args.quiet,
|
||||||
args.legacy, workers=args.workers):
|
args.legacy, workers=args.workers,
|
||||||
|
invalidation_mode=invalidation_mode,
|
||||||
|
stripdir=args.stripdir,
|
||||||
|
prependdir=args.prependdir,
|
||||||
|
optimize=args.opt_levels,
|
||||||
|
limit_sl_dest=args.limit_sl_dest,
|
||||||
|
hardlink_dupes=args.hardlink_dupes):
|
||||||
success = False
|
success = False
|
||||||
return success
|
return success
|
||||||
else:
|
else:
|
||||||
return compile_path(legacy=args.legacy, force=args.force,
|
return compile_path(legacy=args.legacy, force=args.force,
|
||||||
quiet=args.quiet)
|
quiet=args.quiet,
|
||||||
|
invalidation_mode=invalidation_mode)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
if args.quiet < 2:
|
if args.quiet < 2:
|
||||||
print("\n[interrupted]")
|
print("\n[interrupted]")
|
||||||
|
|||||||
3
Lib/concurrent/futures/thread.py
vendored
3
Lib/concurrent/futures/thread.py
vendored
@@ -37,7 +37,8 @@ def _python_exit():
|
|||||||
threading._register_atexit(_python_exit)
|
threading._register_atexit(_python_exit)
|
||||||
|
|
||||||
# At fork, reinitialize the `_global_shutdown_lock` lock in the child process
|
# At fork, reinitialize the `_global_shutdown_lock` lock in the child process
|
||||||
if hasattr(os, 'register_at_fork'):
|
# TODO RUSTPYTHON - _at_fork_reinit is not implemented yet
|
||||||
|
if hasattr(os, 'register_at_fork') and hasattr(_global_shutdown_lock, '_at_fork_reinit'):
|
||||||
os.register_at_fork(before=_global_shutdown_lock.acquire,
|
os.register_at_fork(before=_global_shutdown_lock.acquire,
|
||||||
after_in_child=_global_shutdown_lock._at_fork_reinit,
|
after_in_child=_global_shutdown_lock._at_fork_reinit,
|
||||||
after_in_parent=_global_shutdown_lock.release)
|
after_in_parent=_global_shutdown_lock.release)
|
||||||
|
|||||||
204
Lib/configparser.py
vendored
204
Lib/configparser.py
vendored
@@ -19,36 +19,37 @@ ConfigParser -- responsible for parsing a list of
|
|||||||
inline_comment_prefixes=None, strict=True,
|
inline_comment_prefixes=None, strict=True,
|
||||||
empty_lines_in_values=True, default_section='DEFAULT',
|
empty_lines_in_values=True, default_section='DEFAULT',
|
||||||
interpolation=<unset>, converters=<unset>):
|
interpolation=<unset>, converters=<unset>):
|
||||||
Create the parser. When `defaults' is given, it is initialized into the
|
|
||||||
|
Create the parser. When `defaults` is given, it is initialized into the
|
||||||
dictionary or intrinsic defaults. The keys must be strings, the values
|
dictionary or intrinsic defaults. The keys must be strings, the values
|
||||||
must be appropriate for %()s string interpolation.
|
must be appropriate for %()s string interpolation.
|
||||||
|
|
||||||
When `dict_type' is given, it will be used to create the dictionary
|
When `dict_type` is given, it will be used to create the dictionary
|
||||||
objects for the list of sections, for the options within a section, and
|
objects for the list of sections, for the options within a section, and
|
||||||
for the default values.
|
for the default values.
|
||||||
|
|
||||||
When `delimiters' is given, it will be used as the set of substrings
|
When `delimiters` is given, it will be used as the set of substrings
|
||||||
that divide keys from values.
|
that divide keys from values.
|
||||||
|
|
||||||
When `comment_prefixes' is given, it will be used as the set of
|
When `comment_prefixes` is given, it will be used as the set of
|
||||||
substrings that prefix comments in empty lines. Comments can be
|
substrings that prefix comments in empty lines. Comments can be
|
||||||
indented.
|
indented.
|
||||||
|
|
||||||
When `inline_comment_prefixes' is given, it will be used as the set of
|
When `inline_comment_prefixes` is given, it will be used as the set of
|
||||||
substrings that prefix comments in non-empty lines.
|
substrings that prefix comments in non-empty lines.
|
||||||
|
|
||||||
When `strict` is True, the parser won't allow for any section or option
|
When `strict` is True, the parser won't allow for any section or option
|
||||||
duplicates while reading from a single source (file, string or
|
duplicates while reading from a single source (file, string or
|
||||||
dictionary). Default is True.
|
dictionary). Default is True.
|
||||||
|
|
||||||
When `empty_lines_in_values' is False (default: True), each empty line
|
When `empty_lines_in_values` is False (default: True), each empty line
|
||||||
marks the end of an option. Otherwise, internal empty lines of
|
marks the end of an option. Otherwise, internal empty lines of
|
||||||
a multiline option are kept as part of the value.
|
a multiline option are kept as part of the value.
|
||||||
|
|
||||||
When `allow_no_value' is True (default: False), options without
|
When `allow_no_value` is True (default: False), options without
|
||||||
values are accepted; the value presented for these is None.
|
values are accepted; the value presented for these is None.
|
||||||
|
|
||||||
When `default_section' is given, the name of the special section is
|
When `default_section` is given, the name of the special section is
|
||||||
named accordingly. By default it is called ``"DEFAULT"`` but this can
|
named accordingly. By default it is called ``"DEFAULT"`` but this can
|
||||||
be customized to point to any other valid section name. Its current
|
be customized to point to any other valid section name. Its current
|
||||||
value can be retrieved using the ``parser_instance.default_section``
|
value can be retrieved using the ``parser_instance.default_section``
|
||||||
@@ -56,9 +57,9 @@ ConfigParser -- responsible for parsing a list of
|
|||||||
|
|
||||||
When `interpolation` is given, it should be an Interpolation subclass
|
When `interpolation` is given, it should be an Interpolation subclass
|
||||||
instance. It will be used as the handler for option value
|
instance. It will be used as the handler for option value
|
||||||
pre-processing when using getters. RawConfigParser object s don't do
|
pre-processing when using getters. RawConfigParser objects don't do
|
||||||
any sort of interpolation, whereas ConfigParser uses an instance of
|
any sort of interpolation, whereas ConfigParser uses an instance of
|
||||||
BasicInterpolation. The library also provides a ``zc.buildbot``
|
BasicInterpolation. The library also provides a ``zc.buildout``
|
||||||
inspired ExtendedInterpolation implementation.
|
inspired ExtendedInterpolation implementation.
|
||||||
|
|
||||||
When `converters` is given, it should be a dictionary where each key
|
When `converters` is given, it should be a dictionary where each key
|
||||||
@@ -80,14 +81,14 @@ ConfigParser -- responsible for parsing a list of
|
|||||||
Return list of configuration options for the named section.
|
Return list of configuration options for the named section.
|
||||||
|
|
||||||
read(filenames, encoding=None)
|
read(filenames, encoding=None)
|
||||||
Read and parse the list of named configuration files, given by
|
Read and parse the iterable of named configuration files, given by
|
||||||
name. A single filename is also allowed. Non-existing files
|
name. A single filename is also allowed. Non-existing files
|
||||||
are ignored. Return list of successfully read files.
|
are ignored. Return list of successfully read files.
|
||||||
|
|
||||||
read_file(f, filename=None)
|
read_file(f, filename=None)
|
||||||
Read and parse one configuration file, given as a file object.
|
Read and parse one configuration file, given as a file object.
|
||||||
The filename defaults to f.name; it is only used in error
|
The filename defaults to f.name; it is only used in error
|
||||||
messages (if f has no `name' attribute, the string `<???>' is used).
|
messages (if f has no `name` attribute, the string `<???>` is used).
|
||||||
|
|
||||||
read_string(string)
|
read_string(string)
|
||||||
Read configuration from a given string.
|
Read configuration from a given string.
|
||||||
@@ -103,9 +104,9 @@ ConfigParser -- responsible for parsing a list of
|
|||||||
Return a string value for the named option. All % interpolations are
|
Return a string value for the named option. All % interpolations are
|
||||||
expanded in the return values, based on the defaults passed into the
|
expanded in the return values, based on the defaults passed into the
|
||||||
constructor and the DEFAULT section. Additional substitutions may be
|
constructor and the DEFAULT section. Additional substitutions may be
|
||||||
provided using the `vars' argument, which must be a dictionary whose
|
provided using the `vars` argument, which must be a dictionary whose
|
||||||
contents override any pre-existing defaults. If `option' is a key in
|
contents override any pre-existing defaults. If `option` is a key in
|
||||||
`vars', the value from `vars' is used.
|
`vars`, the value from `vars` is used.
|
||||||
|
|
||||||
getint(section, options, raw=False, vars=None, fallback=_UNSET)
|
getint(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||||
Like get(), but convert value to an integer.
|
Like get(), but convert value to an integer.
|
||||||
@@ -134,28 +135,30 @@ ConfigParser -- responsible for parsing a list of
|
|||||||
|
|
||||||
write(fp, space_around_delimiters=True)
|
write(fp, space_around_delimiters=True)
|
||||||
Write the configuration state in .ini format. If
|
Write the configuration state in .ini format. If
|
||||||
`space_around_delimiters' is True (the default), delimiters
|
`space_around_delimiters` is True (the default), delimiters
|
||||||
between keys and values are surrounded by spaces.
|
between keys and values are surrounded by spaces.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from collections import OrderedDict as _default_dict, ChainMap as _ChainMap
|
from collections import ChainMap as _ChainMap
|
||||||
import functools
|
import functools
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
__all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
||||||
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
||||||
"InterpolationMissingOptionError", "InterpolationSyntaxError",
|
"InterpolationMissingOptionError", "InterpolationSyntaxError",
|
||||||
"ParsingError", "MissingSectionHeaderError",
|
"ParsingError", "MissingSectionHeaderError",
|
||||||
"ConfigParser", "SafeConfigParser", "RawConfigParser",
|
"ConfigParser", "RawConfigParser",
|
||||||
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
|
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
|
||||||
"LegacyInterpolation", "SectionProxy", "ConverterMapping",
|
"LegacyInterpolation", "SectionProxy", "ConverterMapping",
|
||||||
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
|
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH")
|
||||||
|
|
||||||
|
_default_dict = dict
|
||||||
DEFAULTSECT = "DEFAULT"
|
DEFAULTSECT = "DEFAULT"
|
||||||
|
|
||||||
MAX_INTERPOLATION_DEPTH = 10
|
MAX_INTERPOLATION_DEPTH = 10
|
||||||
@@ -295,41 +298,12 @@ class InterpolationDepthError(InterpolationError):
|
|||||||
class ParsingError(Error):
|
class ParsingError(Error):
|
||||||
"""Raised when a configuration file does not follow legal syntax."""
|
"""Raised when a configuration file does not follow legal syntax."""
|
||||||
|
|
||||||
def __init__(self, source=None, filename=None):
|
def __init__(self, source):
|
||||||
# Exactly one of `source'/`filename' arguments has to be given.
|
super().__init__(f'Source contains parsing errors: {source!r}')
|
||||||
# `filename' kept for compatibility.
|
|
||||||
if filename and source:
|
|
||||||
raise ValueError("Cannot specify both `filename' and `source'. "
|
|
||||||
"Use `source'.")
|
|
||||||
elif not filename and not source:
|
|
||||||
raise ValueError("Required argument `source' not given.")
|
|
||||||
elif filename:
|
|
||||||
source = filename
|
|
||||||
Error.__init__(self, 'Source contains parsing errors: %r' % source)
|
|
||||||
self.source = source
|
self.source = source
|
||||||
self.errors = []
|
self.errors = []
|
||||||
self.args = (source, )
|
self.args = (source, )
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
"""Deprecated, use `source'."""
|
|
||||||
warnings.warn(
|
|
||||||
"The 'filename' attribute will be removed in future versions. "
|
|
||||||
"Use 'source' instead.",
|
|
||||||
DeprecationWarning, stacklevel=2
|
|
||||||
)
|
|
||||||
return self.source
|
|
||||||
|
|
||||||
@filename.setter
|
|
||||||
def filename(self, value):
|
|
||||||
"""Deprecated, user `source'."""
|
|
||||||
warnings.warn(
|
|
||||||
"The 'filename' attribute will be removed in future versions. "
|
|
||||||
"Use 'source' instead.",
|
|
||||||
DeprecationWarning, stacklevel=2
|
|
||||||
)
|
|
||||||
self.source = value
|
|
||||||
|
|
||||||
def append(self, lineno, line):
|
def append(self, lineno, line):
|
||||||
self.errors.append((lineno, line))
|
self.errors.append((lineno, line))
|
||||||
self.message += '\n\t[line %2d]: %s' % (lineno, line)
|
self.message += '\n\t[line %2d]: %s' % (lineno, line)
|
||||||
@@ -350,7 +324,7 @@ class MissingSectionHeaderError(ParsingError):
|
|||||||
|
|
||||||
|
|
||||||
# Used in parser getters to indicate the default behaviour when a specific
|
# Used in parser getters to indicate the default behaviour when a specific
|
||||||
# option is not found it to raise an exception. Created to enable `None' as
|
# option is not found it to raise an exception. Created to enable `None` as
|
||||||
# a valid fallback value.
|
# a valid fallback value.
|
||||||
_UNSET = object()
|
_UNSET = object()
|
||||||
|
|
||||||
@@ -384,7 +358,7 @@ class BasicInterpolation(Interpolation):
|
|||||||
would resolve the "%(dir)s" to the value of dir. All reference
|
would resolve the "%(dir)s" to the value of dir. All reference
|
||||||
expansions are done late, on demand. If a user needs to use a bare % in
|
expansions are done late, on demand. If a user needs to use a bare % in
|
||||||
a configuration file, she can escape it by writing %%. Other % usage
|
a configuration file, she can escape it by writing %%. Other % usage
|
||||||
is considered a user error and raises `InterpolationSyntaxError'."""
|
is considered a user error and raises `InterpolationSyntaxError`."""
|
||||||
|
|
||||||
_KEYCRE = re.compile(r"%\(([^)]+)\)s")
|
_KEYCRE = re.compile(r"%\(([^)]+)\)s")
|
||||||
|
|
||||||
@@ -445,7 +419,7 @@ class BasicInterpolation(Interpolation):
|
|||||||
|
|
||||||
class ExtendedInterpolation(Interpolation):
|
class ExtendedInterpolation(Interpolation):
|
||||||
"""Advanced variant of interpolation, supports the syntax used by
|
"""Advanced variant of interpolation, supports the syntax used by
|
||||||
`zc.buildout'. Enables interpolation between sections."""
|
`zc.buildout`. Enables interpolation between sections."""
|
||||||
|
|
||||||
_KEYCRE = re.compile(r"\$\{([^}]+)\}")
|
_KEYCRE = re.compile(r"\$\{([^}]+)\}")
|
||||||
|
|
||||||
@@ -523,6 +497,15 @@ class LegacyInterpolation(Interpolation):
|
|||||||
|
|
||||||
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
|
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
warnings.warn(
|
||||||
|
"LegacyInterpolation has been deprecated since Python 3.2 "
|
||||||
|
"and will be removed from the configparser module in Python 3.13. "
|
||||||
|
"Use BasicInterpolation or ExtendedInterpolation instead.",
|
||||||
|
DeprecationWarning, stacklevel=2
|
||||||
|
)
|
||||||
|
|
||||||
def before_get(self, parser, section, option, value, vars):
|
def before_get(self, parser, section, option, value, vars):
|
||||||
rawval = value
|
rawval = value
|
||||||
depth = MAX_INTERPOLATION_DEPTH
|
depth = MAX_INTERPOLATION_DEPTH
|
||||||
@@ -561,7 +544,7 @@ class RawConfigParser(MutableMapping):
|
|||||||
# Regular expressions for parsing section headers and options
|
# Regular expressions for parsing section headers and options
|
||||||
_SECT_TMPL = r"""
|
_SECT_TMPL = r"""
|
||||||
\[ # [
|
\[ # [
|
||||||
(?P<header>[^]]+) # very permissive!
|
(?P<header>.+) # very permissive!
|
||||||
\] # ]
|
\] # ]
|
||||||
"""
|
"""
|
||||||
_OPT_TMPL = r"""
|
_OPT_TMPL = r"""
|
||||||
@@ -609,9 +592,6 @@ class RawConfigParser(MutableMapping):
|
|||||||
self._converters = ConverterMapping(self)
|
self._converters = ConverterMapping(self)
|
||||||
self._proxies = self._dict()
|
self._proxies = self._dict()
|
||||||
self._proxies[default_section] = SectionProxy(self, default_section)
|
self._proxies[default_section] = SectionProxy(self, default_section)
|
||||||
if defaults:
|
|
||||||
for key, value in defaults.items():
|
|
||||||
self._defaults[self.optionxform(key)] = value
|
|
||||||
self._delimiters = tuple(delimiters)
|
self._delimiters = tuple(delimiters)
|
||||||
if delimiters == ('=', ':'):
|
if delimiters == ('=', ':'):
|
||||||
self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
|
self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
|
||||||
@@ -634,8 +614,15 @@ class RawConfigParser(MutableMapping):
|
|||||||
self._interpolation = self._DEFAULT_INTERPOLATION
|
self._interpolation = self._DEFAULT_INTERPOLATION
|
||||||
if self._interpolation is None:
|
if self._interpolation is None:
|
||||||
self._interpolation = Interpolation()
|
self._interpolation = Interpolation()
|
||||||
|
if not isinstance(self._interpolation, Interpolation):
|
||||||
|
raise TypeError(
|
||||||
|
f"interpolation= must be None or an instance of Interpolation;"
|
||||||
|
f" got an object of type {type(self._interpolation)}"
|
||||||
|
)
|
||||||
if converters is not _UNSET:
|
if converters is not _UNSET:
|
||||||
self._converters.update(converters)
|
self._converters.update(converters)
|
||||||
|
if defaults:
|
||||||
|
self._read_defaults(defaults)
|
||||||
|
|
||||||
def defaults(self):
|
def defaults(self):
|
||||||
return self._defaults
|
return self._defaults
|
||||||
@@ -676,19 +663,20 @@ class RawConfigParser(MutableMapping):
|
|||||||
return list(opts.keys())
|
return list(opts.keys())
|
||||||
|
|
||||||
def read(self, filenames, encoding=None):
|
def read(self, filenames, encoding=None):
|
||||||
"""Read and parse a filename or a list of filenames.
|
"""Read and parse a filename or an iterable of filenames.
|
||||||
|
|
||||||
Files that cannot be opened are silently ignored; this is
|
Files that cannot be opened are silently ignored; this is
|
||||||
designed so that you can specify a list of potential
|
designed so that you can specify an iterable of potential
|
||||||
configuration file locations (e.g. current directory, user's
|
configuration file locations (e.g. current directory, user's
|
||||||
home directory, systemwide directory), and all existing
|
home directory, systemwide directory), and all existing
|
||||||
configuration files in the list will be read. A single
|
configuration files in the iterable will be read. A single
|
||||||
filename may also be given.
|
filename may also be given.
|
||||||
|
|
||||||
Return list of successfully read files.
|
Return list of successfully read files.
|
||||||
"""
|
"""
|
||||||
if isinstance(filenames, str):
|
if isinstance(filenames, (str, bytes, os.PathLike)):
|
||||||
filenames = [filenames]
|
filenames = [filenames]
|
||||||
|
encoding = io.text_encoding(encoding)
|
||||||
read_ok = []
|
read_ok = []
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
try:
|
try:
|
||||||
@@ -696,16 +684,18 @@ class RawConfigParser(MutableMapping):
|
|||||||
self._read(fp, filename)
|
self._read(fp, filename)
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
|
if isinstance(filename, os.PathLike):
|
||||||
|
filename = os.fspath(filename)
|
||||||
read_ok.append(filename)
|
read_ok.append(filename)
|
||||||
return read_ok
|
return read_ok
|
||||||
|
|
||||||
def read_file(self, f, source=None):
|
def read_file(self, f, source=None):
|
||||||
"""Like read() but the argument must be a file-like object.
|
"""Like read() but the argument must be a file-like object.
|
||||||
|
|
||||||
The `f' argument must be iterable, returning one line at a time.
|
The `f` argument must be iterable, returning one line at a time.
|
||||||
Optional second argument is the `source' specifying the name of the
|
Optional second argument is the `source` specifying the name of the
|
||||||
file being read. If not given, it is taken from f.name. If `f' has no
|
file being read. If not given, it is taken from f.name. If `f` has no
|
||||||
`name' attribute, `<???>' is used.
|
`name` attribute, `<???>` is used.
|
||||||
"""
|
"""
|
||||||
if source is None:
|
if source is None:
|
||||||
try:
|
try:
|
||||||
@@ -729,7 +719,7 @@ class RawConfigParser(MutableMapping):
|
|||||||
All types held in the dictionary are converted to strings during
|
All types held in the dictionary are converted to strings during
|
||||||
reading, including section names, option names and keys.
|
reading, including section names, option names and keys.
|
||||||
|
|
||||||
Optional second argument is the `source' specifying the name of the
|
Optional second argument is the `source` specifying the name of the
|
||||||
dictionary being read.
|
dictionary being read.
|
||||||
"""
|
"""
|
||||||
elements_added = set()
|
elements_added = set()
|
||||||
@@ -750,27 +740,18 @@ class RawConfigParser(MutableMapping):
|
|||||||
elements_added.add((section, key))
|
elements_added.add((section, key))
|
||||||
self.set(section, key, value)
|
self.set(section, key, value)
|
||||||
|
|
||||||
def readfp(self, fp, filename=None):
|
|
||||||
"""Deprecated, use read_file instead."""
|
|
||||||
warnings.warn(
|
|
||||||
"This method will be removed in future versions. "
|
|
||||||
"Use 'parser.read_file()' instead.",
|
|
||||||
DeprecationWarning, stacklevel=2
|
|
||||||
)
|
|
||||||
self.read_file(fp, source=filename)
|
|
||||||
|
|
||||||
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||||
"""Get an option value for a given section.
|
"""Get an option value for a given section.
|
||||||
|
|
||||||
If `vars' is provided, it must be a dictionary. The option is looked up
|
If `vars` is provided, it must be a dictionary. The option is looked up
|
||||||
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
in `vars` (if provided), `section`, and in `DEFAULTSECT` in that order.
|
||||||
If the key is not found and `fallback' is provided, it is used as
|
If the key is not found and `fallback` is provided, it is used as
|
||||||
a fallback value. `None' can be provided as a `fallback' value.
|
a fallback value. `None` can be provided as a `fallback` value.
|
||||||
|
|
||||||
If interpolation is enabled and the optional argument `raw' is False,
|
If interpolation is enabled and the optional argument `raw` is False,
|
||||||
all interpolations are expanded in the return values.
|
all interpolations are expanded in the return values.
|
||||||
|
|
||||||
Arguments `raw', `vars', and `fallback' are keyword only.
|
Arguments `raw`, `vars`, and `fallback` are keyword only.
|
||||||
|
|
||||||
The section DEFAULT is special.
|
The section DEFAULT is special.
|
||||||
"""
|
"""
|
||||||
@@ -830,8 +811,8 @@ class RawConfigParser(MutableMapping):
|
|||||||
|
|
||||||
All % interpolations are expanded in the return values, based on the
|
All % interpolations are expanded in the return values, based on the
|
||||||
defaults passed into the constructor, unless the optional argument
|
defaults passed into the constructor, unless the optional argument
|
||||||
`raw' is true. Additional substitutions may be provided using the
|
`raw` is true. Additional substitutions may be provided using the
|
||||||
`vars' argument, which must be a dictionary whose contents overrides
|
`vars` argument, which must be a dictionary whose contents overrides
|
||||||
any pre-existing defaults.
|
any pre-existing defaults.
|
||||||
|
|
||||||
The section DEFAULT is special.
|
The section DEFAULT is special.
|
||||||
@@ -844,6 +825,7 @@ class RawConfigParser(MutableMapping):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
if section != self.default_section:
|
if section != self.default_section:
|
||||||
raise NoSectionError(section)
|
raise NoSectionError(section)
|
||||||
|
orig_keys = list(d.keys())
|
||||||
# Update with the entry specific variables
|
# Update with the entry specific variables
|
||||||
if vars:
|
if vars:
|
||||||
for key, value in vars.items():
|
for key, value in vars.items():
|
||||||
@@ -852,7 +834,7 @@ class RawConfigParser(MutableMapping):
|
|||||||
section, option, d[option], d)
|
section, option, d[option], d)
|
||||||
if raw:
|
if raw:
|
||||||
value_getter = lambda option: d[option]
|
value_getter = lambda option: d[option]
|
||||||
return [(option, value_getter(option)) for option in d.keys()]
|
return [(option, value_getter(option)) for option in orig_keys]
|
||||||
|
|
||||||
def popitem(self):
|
def popitem(self):
|
||||||
"""Remove a section from the parser and return it as
|
"""Remove a section from the parser and return it as
|
||||||
@@ -872,8 +854,8 @@ class RawConfigParser(MutableMapping):
|
|||||||
|
|
||||||
def has_option(self, section, option):
|
def has_option(self, section, option):
|
||||||
"""Check for the existence of a given option in a given section.
|
"""Check for the existence of a given option in a given section.
|
||||||
If the specified `section' is None or an empty string, DEFAULT is
|
If the specified `section` is None or an empty string, DEFAULT is
|
||||||
assumed. If the specified `section' does not exist, returns False."""
|
assumed. If the specified `section` does not exist, returns False."""
|
||||||
if not section or section == self.default_section:
|
if not section or section == self.default_section:
|
||||||
option = self.optionxform(option)
|
option = self.optionxform(option)
|
||||||
return option in self._defaults
|
return option in self._defaults
|
||||||
@@ -901,8 +883,11 @@ class RawConfigParser(MutableMapping):
|
|||||||
def write(self, fp, space_around_delimiters=True):
|
def write(self, fp, space_around_delimiters=True):
|
||||||
"""Write an .ini-format representation of the configuration state.
|
"""Write an .ini-format representation of the configuration state.
|
||||||
|
|
||||||
If `space_around_delimiters' is True (the default), delimiters
|
If `space_around_delimiters` is True (the default), delimiters
|
||||||
between keys and values are surrounded by spaces.
|
between keys and values are surrounded by spaces.
|
||||||
|
|
||||||
|
Please note that comments in the original configuration file are not
|
||||||
|
preserved when writing the configuration back.
|
||||||
"""
|
"""
|
||||||
if space_around_delimiters:
|
if space_around_delimiters:
|
||||||
d = " {} ".format(self._delimiters[0])
|
d = " {} ".format(self._delimiters[0])
|
||||||
@@ -916,7 +901,7 @@ class RawConfigParser(MutableMapping):
|
|||||||
self._sections[section].items(), d)
|
self._sections[section].items(), d)
|
||||||
|
|
||||||
def _write_section(self, fp, section_name, section_items, delimiter):
|
def _write_section(self, fp, section_name, section_items, delimiter):
|
||||||
"""Write a single section to the specified `fp'."""
|
"""Write a single section to the specified `fp`."""
|
||||||
fp.write("[{}]\n".format(section_name))
|
fp.write("[{}]\n".format(section_name))
|
||||||
for key, value in section_items:
|
for key, value in section_items:
|
||||||
value = self._interpolation.before_write(self, section_name, key,
|
value = self._interpolation.before_write(self, section_name, key,
|
||||||
@@ -959,7 +944,8 @@ class RawConfigParser(MutableMapping):
|
|||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
# To conform with the mapping protocol, overwrites existing values in
|
# To conform with the mapping protocol, overwrites existing values in
|
||||||
# the section.
|
# the section.
|
||||||
|
if key in self and self[key] is value:
|
||||||
|
return
|
||||||
# XXX this is not atomic if read_dict fails at any point. Then again,
|
# XXX this is not atomic if read_dict fails at any point. Then again,
|
||||||
# no update method in configparser is atomic in this implementation.
|
# no update method in configparser is atomic in this implementation.
|
||||||
if key == self.default_section:
|
if key == self.default_section:
|
||||||
@@ -989,8 +975,8 @@ class RawConfigParser(MutableMapping):
|
|||||||
"""Parse a sectioned configuration file.
|
"""Parse a sectioned configuration file.
|
||||||
|
|
||||||
Each section in a configuration file contains a header, indicated by
|
Each section in a configuration file contains a header, indicated by
|
||||||
a name in square brackets (`[]'), plus key/value options, indicated by
|
a name in square brackets (`[]`), plus key/value options, indicated by
|
||||||
`name' and `value' delimited with a specific substring (`=' or `:' by
|
`name` and `value` delimited with a specific substring (`=` or `:` by
|
||||||
default).
|
default).
|
||||||
|
|
||||||
Values can span multiple lines, as long as they are indented deeper
|
Values can span multiple lines, as long as they are indented deeper
|
||||||
@@ -998,9 +984,9 @@ class RawConfigParser(MutableMapping):
|
|||||||
lines may be treated as parts of multiline values or ignored.
|
lines may be treated as parts of multiline values or ignored.
|
||||||
|
|
||||||
Configuration files may include comments, prefixed by specific
|
Configuration files may include comments, prefixed by specific
|
||||||
characters (`#' and `;' by default). Comments may appear on their own
|
characters (`#` and `;` by default). Comments may appear on their own
|
||||||
in an otherwise empty line or may be entered in lines holding values or
|
in an otherwise empty line or may be entered in lines holding values or
|
||||||
section names.
|
section names. Please note that comments get stripped off when reading configuration files.
|
||||||
"""
|
"""
|
||||||
elements_added = set()
|
elements_added = set()
|
||||||
cursect = None # None, or a dictionary
|
cursect = None # None, or a dictionary
|
||||||
@@ -1119,6 +1105,12 @@ class RawConfigParser(MutableMapping):
|
|||||||
section,
|
section,
|
||||||
name, val)
|
name, val)
|
||||||
|
|
||||||
|
def _read_defaults(self, defaults):
|
||||||
|
"""Read the defaults passed in the initializer.
|
||||||
|
Note: values can be non-string."""
|
||||||
|
for key, value in defaults.items():
|
||||||
|
self._defaults[self.optionxform(key)] = value
|
||||||
|
|
||||||
def _handle_error(self, exc, fpname, lineno, line):
|
def _handle_error(self, exc, fpname, lineno, line):
|
||||||
if not exc:
|
if not exc:
|
||||||
exc = ParsingError(fpname)
|
exc = ParsingError(fpname)
|
||||||
@@ -1135,7 +1127,7 @@ class RawConfigParser(MutableMapping):
|
|||||||
sectiondict = self._sections[section]
|
sectiondict = self._sections[section]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if section != self.default_section:
|
if section != self.default_section:
|
||||||
raise NoSectionError(section)
|
raise NoSectionError(section) from None
|
||||||
# Update with the entry specific variables
|
# Update with the entry specific variables
|
||||||
vardict = {}
|
vardict = {}
|
||||||
if vars:
|
if vars:
|
||||||
@@ -1196,18 +1188,18 @@ class ConfigParser(RawConfigParser):
|
|||||||
self._validate_value_types(section=section)
|
self._validate_value_types(section=section)
|
||||||
super().add_section(section)
|
super().add_section(section)
|
||||||
|
|
||||||
|
def _read_defaults(self, defaults):
|
||||||
|
"""Reads the defaults passed in the initializer, implicitly converting
|
||||||
|
values to strings like the rest of the API.
|
||||||
|
|
||||||
class SafeConfigParser(ConfigParser):
|
Does not perform interpolation for backwards compatibility.
|
||||||
"""ConfigParser alias for backwards compatibility purposes."""
|
"""
|
||||||
|
try:
|
||||||
def __init__(self, *args, **kwargs):
|
hold_interpolation = self._interpolation
|
||||||
super().__init__(*args, **kwargs)
|
self._interpolation = Interpolation()
|
||||||
warnings.warn(
|
self.read_dict({self.default_section: defaults})
|
||||||
"The SafeConfigParser class has been renamed to ConfigParser "
|
finally:
|
||||||
"in Python 3.2. This alias will be removed in future versions."
|
self._interpolation = hold_interpolation
|
||||||
" Use ConfigParser directly instead.",
|
|
||||||
DeprecationWarning, stacklevel=2
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SectionProxy(MutableMapping):
|
class SectionProxy(MutableMapping):
|
||||||
|
|||||||
259
Lib/contextlib.py
vendored
259
Lib/contextlib.py
vendored
@@ -1,20 +1,25 @@
|
|||||||
"""Utilities for with-statement contexts. See PEP 343."""
|
"""Utilities for with-statement contexts. See PEP 343."""
|
||||||
import abc
|
import abc
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import _collections_abc
|
import _collections_abc
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from types import MethodType, GenericAlias
|
||||||
|
|
||||||
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
||||||
"AbstractContextManager", "AbstractAsyncContextManager",
|
"AbstractContextManager", "AbstractAsyncContextManager",
|
||||||
"AsyncExitStack", "ContextDecorator", "ExitStack",
|
"AsyncExitStack", "ContextDecorator", "ExitStack",
|
||||||
"redirect_stdout", "redirect_stderr", "suppress"]
|
"redirect_stdout", "redirect_stderr", "suppress", "aclosing",
|
||||||
|
"chdir"]
|
||||||
|
|
||||||
|
|
||||||
class AbstractContextManager(abc.ABC):
|
class AbstractContextManager(abc.ABC):
|
||||||
|
|
||||||
"""An abstract base class for context managers."""
|
"""An abstract base class for context managers."""
|
||||||
|
|
||||||
|
__class_getitem__ = classmethod(GenericAlias)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""Return `self` upon entering the runtime context."""
|
"""Return `self` upon entering the runtime context."""
|
||||||
return self
|
return self
|
||||||
@@ -35,6 +40,8 @@ class AbstractAsyncContextManager(abc.ABC):
|
|||||||
|
|
||||||
"""An abstract base class for asynchronous context managers."""
|
"""An abstract base class for asynchronous context managers."""
|
||||||
|
|
||||||
|
__class_getitem__ = classmethod(GenericAlias)
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
"""Return `self` upon entering the runtime context."""
|
"""Return `self` upon entering the runtime context."""
|
||||||
return self
|
return self
|
||||||
@@ -75,6 +82,22 @@ class ContextDecorator(object):
|
|||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncContextDecorator(object):
|
||||||
|
"A base class or mixin that enables async context managers to work as decorators."
|
||||||
|
|
||||||
|
def _recreate_cm(self):
|
||||||
|
"""Return a recreated instance of self.
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
@wraps(func)
|
||||||
|
async def inner(*args, **kwds):
|
||||||
|
async with self._recreate_cm():
|
||||||
|
return await func(*args, **kwds)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
class _GeneratorContextManagerBase:
|
class _GeneratorContextManagerBase:
|
||||||
"""Shared functionality for @contextmanager and @asynccontextmanager."""
|
"""Shared functionality for @contextmanager and @asynccontextmanager."""
|
||||||
|
|
||||||
@@ -92,18 +115,20 @@ class _GeneratorContextManagerBase:
|
|||||||
# for the class instead.
|
# for the class instead.
|
||||||
# See http://bugs.python.org/issue19404 for more details.
|
# See http://bugs.python.org/issue19404 for more details.
|
||||||
|
|
||||||
|
|
||||||
class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|
||||||
AbstractContextManager,
|
|
||||||
ContextDecorator):
|
|
||||||
"""Helper for @contextmanager decorator."""
|
|
||||||
|
|
||||||
def _recreate_cm(self):
|
def _recreate_cm(self):
|
||||||
# _GCM instances are one-shot context managers, so the
|
# _GCMB instances are one-shot context managers, so the
|
||||||
# CM must be recreated each time a decorated function is
|
# CM must be recreated each time a decorated function is
|
||||||
# called
|
# called
|
||||||
return self.__class__(self.func, self.args, self.kwds)
|
return self.__class__(self.func, self.args, self.kwds)
|
||||||
|
|
||||||
|
|
||||||
|
class _GeneratorContextManager(
|
||||||
|
_GeneratorContextManagerBase,
|
||||||
|
AbstractContextManager,
|
||||||
|
ContextDecorator,
|
||||||
|
):
|
||||||
|
"""Helper for @contextmanager decorator."""
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
# do not keep args and kwds alive unnecessarily
|
# do not keep args and kwds alive unnecessarily
|
||||||
# they are only needed for recreation, which is not possible anymore
|
# they are only needed for recreation, which is not possible anymore
|
||||||
@@ -113,21 +138,24 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise RuntimeError("generator didn't yield") from None
|
raise RuntimeError("generator didn't yield") from None
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
def __exit__(self, typ, value, traceback):
|
||||||
if type is None:
|
if typ is None:
|
||||||
try:
|
try:
|
||||||
next(self.gen)
|
next(self.gen)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("generator didn't stop")
|
try:
|
||||||
|
raise RuntimeError("generator didn't stop")
|
||||||
|
finally:
|
||||||
|
self.gen.close()
|
||||||
else:
|
else:
|
||||||
if value is None:
|
if value is None:
|
||||||
# Need to force instantiation so we can reliably
|
# Need to force instantiation so we can reliably
|
||||||
# tell if we get the same exception back
|
# tell if we get the same exception back
|
||||||
value = type()
|
value = typ()
|
||||||
try:
|
try:
|
||||||
self.gen.throw(type, value, traceback)
|
self.gen.throw(value)
|
||||||
except StopIteration as exc:
|
except StopIteration as exc:
|
||||||
# Suppress StopIteration *unless* it's the same exception that
|
# Suppress StopIteration *unless* it's the same exception that
|
||||||
# was passed to throw(). This prevents a StopIteration
|
# was passed to throw(). This prevents a StopIteration
|
||||||
@@ -136,75 +164,109 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
# Don't re-raise the passed in exception. (issue27122)
|
# Don't re-raise the passed in exception. (issue27122)
|
||||||
if exc is value:
|
if exc is value:
|
||||||
|
exc.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
# Likewise, avoid suppressing if a StopIteration exception
|
# Avoid suppressing if a StopIteration exception
|
||||||
# was passed to throw() and later wrapped into a RuntimeError
|
# was passed to throw() and later wrapped into a RuntimeError
|
||||||
# (see PEP 479).
|
# (see PEP 479 for sync generators; async generators also
|
||||||
if type is StopIteration and exc.__cause__ is value:
|
# have this behavior). But do this only if the exception wrapped
|
||||||
|
# by the RuntimeError is actually Stop(Async)Iteration (see
|
||||||
|
# issue29692).
|
||||||
|
if (
|
||||||
|
isinstance(value, StopIteration)
|
||||||
|
and exc.__cause__ is value
|
||||||
|
):
|
||||||
|
value.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
raise
|
raise
|
||||||
except:
|
except BaseException as exc:
|
||||||
# only re-raise if it's *not* the exception that was
|
# only re-raise if it's *not* the exception that was
|
||||||
# passed to throw(), because __exit__() must not raise
|
# passed to throw(), because __exit__() must not raise
|
||||||
# an exception unless __exit__() itself failed. But throw()
|
# an exception unless __exit__() itself failed. But throw()
|
||||||
# has to raise the exception to signal propagation, so this
|
# has to raise the exception to signal propagation, so this
|
||||||
# fixes the impedance mismatch between the throw() protocol
|
# fixes the impedance mismatch between the throw() protocol
|
||||||
# and the __exit__() protocol.
|
# and the __exit__() protocol.
|
||||||
#
|
if exc is not value:
|
||||||
# This cannot use 'except BaseException as exc' (as in the
|
raise
|
||||||
# async implementation) to maintain compatibility with
|
exc.__traceback__ = traceback
|
||||||
# Python 2, where old-style class exceptions are not caught
|
return False
|
||||||
# by 'except BaseException'.
|
try:
|
||||||
if sys.exc_info()[1] is value:
|
raise RuntimeError("generator didn't stop after throw()")
|
||||||
return False
|
finally:
|
||||||
raise
|
self.gen.close()
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
|
||||||
|
|
||||||
|
class _AsyncGeneratorContextManager(
|
||||||
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
_GeneratorContextManagerBase,
|
||||||
AbstractAsyncContextManager):
|
AbstractAsyncContextManager,
|
||||||
"""Helper for @asynccontextmanager."""
|
AsyncContextDecorator,
|
||||||
|
):
|
||||||
|
"""Helper for @asynccontextmanager decorator."""
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
|
# do not keep args and kwds alive unnecessarily
|
||||||
|
# they are only needed for recreation, which is not possible anymore
|
||||||
|
del self.args, self.kwds, self.func
|
||||||
try:
|
try:
|
||||||
return await self.gen.__anext__()
|
return await anext(self.gen)
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
raise RuntimeError("generator didn't yield") from None
|
raise RuntimeError("generator didn't yield") from None
|
||||||
|
|
||||||
async def __aexit__(self, typ, value, traceback):
|
async def __aexit__(self, typ, value, traceback):
|
||||||
if typ is None:
|
if typ is None:
|
||||||
try:
|
try:
|
||||||
await self.gen.__anext__()
|
await anext(self.gen)
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
return
|
return False
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("generator didn't stop")
|
try:
|
||||||
|
raise RuntimeError("generator didn't stop")
|
||||||
|
finally:
|
||||||
|
await self.gen.aclose()
|
||||||
else:
|
else:
|
||||||
if value is None:
|
if value is None:
|
||||||
|
# Need to force instantiation so we can reliably
|
||||||
|
# tell if we get the same exception back
|
||||||
value = typ()
|
value = typ()
|
||||||
# See _GeneratorContextManager.__exit__ for comments on subtleties
|
|
||||||
# in this implementation
|
|
||||||
try:
|
try:
|
||||||
await self.gen.athrow(typ, value, traceback)
|
await self.gen.athrow(value)
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
|
||||||
except StopAsyncIteration as exc:
|
except StopAsyncIteration as exc:
|
||||||
|
# Suppress StopIteration *unless* it's the same exception that
|
||||||
|
# was passed to throw(). This prevents a StopIteration
|
||||||
|
# raised inside the "with" statement from being suppressed.
|
||||||
return exc is not value
|
return exc is not value
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
|
# Don't re-raise the passed in exception. (issue27122)
|
||||||
if exc is value:
|
if exc is value:
|
||||||
|
exc.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
# Avoid suppressing if a StopIteration exception
|
# Avoid suppressing if a Stop(Async)Iteration exception
|
||||||
# was passed to throw() and later wrapped into a RuntimeError
|
# was passed to athrow() and later wrapped into a RuntimeError
|
||||||
# (see PEP 479 for sync generators; async generators also
|
# (see PEP 479 for sync generators; async generators also
|
||||||
# have this behavior). But do this only if the exception wrapped
|
# have this behavior). But do this only if the exception wrapped
|
||||||
# by the RuntimeError is actully Stop(Async)Iteration (see
|
# by the RuntimeError is actually Stop(Async)Iteration (see
|
||||||
# issue29692).
|
# issue29692).
|
||||||
if isinstance(value, (StopIteration, StopAsyncIteration)):
|
if (
|
||||||
if exc.__cause__ is value:
|
isinstance(value, (StopIteration, StopAsyncIteration))
|
||||||
return False
|
and exc.__cause__ is value
|
||||||
|
):
|
||||||
|
value.__traceback__ = traceback
|
||||||
|
return False
|
||||||
raise
|
raise
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
|
# only re-raise if it's *not* the exception that was
|
||||||
|
# passed to throw(), because __exit__() must not raise
|
||||||
|
# an exception unless __exit__() itself failed. But throw()
|
||||||
|
# has to raise the exception to signal propagation, so this
|
||||||
|
# fixes the impedance mismatch between the throw() protocol
|
||||||
|
# and the __exit__() protocol.
|
||||||
if exc is not value:
|
if exc is not value:
|
||||||
raise
|
raise
|
||||||
|
exc.__traceback__ = traceback
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
raise RuntimeError("generator didn't stop after athrow()")
|
||||||
|
finally:
|
||||||
|
await self.gen.aclose()
|
||||||
|
|
||||||
|
|
||||||
def contextmanager(func):
|
def contextmanager(func):
|
||||||
@@ -298,6 +360,32 @@ class closing(AbstractContextManager):
|
|||||||
self.thing.close()
|
self.thing.close()
|
||||||
|
|
||||||
|
|
||||||
|
class aclosing(AbstractAsyncContextManager):
|
||||||
|
"""Async context manager for safely finalizing an asynchronously cleaned-up
|
||||||
|
resource such as an async generator, calling its ``aclose()`` method.
|
||||||
|
|
||||||
|
Code like this:
|
||||||
|
|
||||||
|
async with aclosing(<module>.fetch(<arguments>)) as agen:
|
||||||
|
<block>
|
||||||
|
|
||||||
|
is equivalent to this:
|
||||||
|
|
||||||
|
agen = <module>.fetch(<arguments>)
|
||||||
|
try:
|
||||||
|
<block>
|
||||||
|
finally:
|
||||||
|
await agen.aclose()
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, thing):
|
||||||
|
self.thing = thing
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self.thing
|
||||||
|
async def __aexit__(self, *exc_info):
|
||||||
|
await self.thing.aclose()
|
||||||
|
|
||||||
|
|
||||||
class _RedirectStream(AbstractContextManager):
|
class _RedirectStream(AbstractContextManager):
|
||||||
|
|
||||||
_stream = None
|
_stream = None
|
||||||
@@ -365,7 +453,16 @@ class suppress(AbstractContextManager):
|
|||||||
# exactly reproduce the limitations of the CPython interpreter.
|
# exactly reproduce the limitations of the CPython interpreter.
|
||||||
#
|
#
|
||||||
# See http://bugs.python.org/issue12029 for more details
|
# See http://bugs.python.org/issue12029 for more details
|
||||||
return exctype is not None and issubclass(exctype, self._exceptions)
|
if exctype is None:
|
||||||
|
return
|
||||||
|
if issubclass(exctype, self._exceptions):
|
||||||
|
return True
|
||||||
|
if issubclass(exctype, BaseExceptionGroup):
|
||||||
|
match, rest = excinst.split(self._exceptions)
|
||||||
|
if rest is None:
|
||||||
|
return True
|
||||||
|
raise rest
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class _BaseExitStack:
|
class _BaseExitStack:
|
||||||
@@ -373,12 +470,10 @@ class _BaseExitStack:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_exit_wrapper(cm, cm_exit):
|
def _create_exit_wrapper(cm, cm_exit):
|
||||||
def _exit_wrapper(exc_type, exc, tb):
|
return MethodType(cm_exit, cm)
|
||||||
return cm_exit(cm, exc_type, exc, tb)
|
|
||||||
return _exit_wrapper
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_cb_wrapper(callback, *args, **kwds):
|
def _create_cb_wrapper(callback, /, *args, **kwds):
|
||||||
def _exit_wrapper(exc_type, exc, tb):
|
def _exit_wrapper(exc_type, exc, tb):
|
||||||
callback(*args, **kwds)
|
callback(*args, **kwds)
|
||||||
return _exit_wrapper
|
return _exit_wrapper
|
||||||
@@ -421,13 +516,18 @@ class _BaseExitStack:
|
|||||||
"""
|
"""
|
||||||
# We look up the special methods on the type to match the with
|
# We look up the special methods on the type to match the with
|
||||||
# statement.
|
# statement.
|
||||||
_cm_type = type(cm)
|
cls = type(cm)
|
||||||
_exit = _cm_type.__exit__
|
try:
|
||||||
result = _cm_type.__enter__(cm)
|
_enter = cls.__enter__
|
||||||
|
_exit = cls.__exit__
|
||||||
|
except AttributeError:
|
||||||
|
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
|
||||||
|
f"not support the context manager protocol") from None
|
||||||
|
result = _enter(cm)
|
||||||
self._push_cm_exit(cm, _exit)
|
self._push_cm_exit(cm, _exit)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def callback(self, callback, *args, **kwds):
|
def callback(self, callback, /, *args, **kwds):
|
||||||
"""Registers an arbitrary callback and arguments.
|
"""Registers an arbitrary callback and arguments.
|
||||||
|
|
||||||
Cannot suppress exceptions.
|
Cannot suppress exceptions.
|
||||||
@@ -443,7 +543,6 @@ class _BaseExitStack:
|
|||||||
def _push_cm_exit(self, cm, cm_exit):
|
def _push_cm_exit(self, cm, cm_exit):
|
||||||
"""Helper to correctly register callbacks to __exit__ methods."""
|
"""Helper to correctly register callbacks to __exit__ methods."""
|
||||||
_exit_wrapper = self._create_exit_wrapper(cm, cm_exit)
|
_exit_wrapper = self._create_exit_wrapper(cm, cm_exit)
|
||||||
_exit_wrapper.__self__ = cm
|
|
||||||
self._push_exit_callback(_exit_wrapper, True)
|
self._push_exit_callback(_exit_wrapper, True)
|
||||||
|
|
||||||
def _push_exit_callback(self, callback, is_sync=True):
|
def _push_exit_callback(self, callback, is_sync=True):
|
||||||
@@ -475,10 +574,10 @@ class ExitStack(_BaseExitStack, AbstractContextManager):
|
|||||||
# Context may not be correct, so find the end of the chain
|
# Context may not be correct, so find the end of the chain
|
||||||
while 1:
|
while 1:
|
||||||
exc_context = new_exc.__context__
|
exc_context = new_exc.__context__
|
||||||
if exc_context is old_exc:
|
if exc_context is None or exc_context is old_exc:
|
||||||
# Context is already set correctly (see issue 20317)
|
# Context is already set correctly (see issue 20317)
|
||||||
return
|
return
|
||||||
if exc_context is None or exc_context is frame_exc:
|
if exc_context is frame_exc:
|
||||||
break
|
break
|
||||||
new_exc = exc_context
|
new_exc = exc_context
|
||||||
# Change the end of the chain to point to the exception
|
# Change the end of the chain to point to the exception
|
||||||
@@ -535,12 +634,10 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_async_exit_wrapper(cm, cm_exit):
|
def _create_async_exit_wrapper(cm, cm_exit):
|
||||||
async def _exit_wrapper(exc_type, exc, tb):
|
return MethodType(cm_exit, cm)
|
||||||
return await cm_exit(cm, exc_type, exc, tb)
|
|
||||||
return _exit_wrapper
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_async_cb_wrapper(callback, *args, **kwds):
|
def _create_async_cb_wrapper(callback, /, *args, **kwds):
|
||||||
async def _exit_wrapper(exc_type, exc, tb):
|
async def _exit_wrapper(exc_type, exc, tb):
|
||||||
await callback(*args, **kwds)
|
await callback(*args, **kwds)
|
||||||
return _exit_wrapper
|
return _exit_wrapper
|
||||||
@@ -551,9 +648,15 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|||||||
If successful, also pushes its __aexit__ method as a callback and
|
If successful, also pushes its __aexit__ method as a callback and
|
||||||
returns the result of the __aenter__ method.
|
returns the result of the __aenter__ method.
|
||||||
"""
|
"""
|
||||||
_cm_type = type(cm)
|
cls = type(cm)
|
||||||
_exit = _cm_type.__aexit__
|
try:
|
||||||
result = await _cm_type.__aenter__(cm)
|
_enter = cls.__aenter__
|
||||||
|
_exit = cls.__aexit__
|
||||||
|
except AttributeError:
|
||||||
|
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
|
||||||
|
f"not support the asynchronous context manager protocol"
|
||||||
|
) from None
|
||||||
|
result = await _enter(cm)
|
||||||
self._push_async_cm_exit(cm, _exit)
|
self._push_async_cm_exit(cm, _exit)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -575,7 +678,7 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|||||||
self._push_async_cm_exit(exit, exit_method)
|
self._push_async_cm_exit(exit, exit_method)
|
||||||
return exit # Allow use as a decorator
|
return exit # Allow use as a decorator
|
||||||
|
|
||||||
def push_async_callback(self, callback, *args, **kwds):
|
def push_async_callback(self, callback, /, *args, **kwds):
|
||||||
"""Registers an arbitrary coroutine function and arguments.
|
"""Registers an arbitrary coroutine function and arguments.
|
||||||
|
|
||||||
Cannot suppress exceptions.
|
Cannot suppress exceptions.
|
||||||
@@ -596,7 +699,6 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|||||||
"""Helper to correctly register coroutine function to __aexit__
|
"""Helper to correctly register coroutine function to __aexit__
|
||||||
method."""
|
method."""
|
||||||
_exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit)
|
_exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit)
|
||||||
_exit_wrapper.__self__ = cm
|
|
||||||
self._push_exit_callback(_exit_wrapper, False)
|
self._push_exit_callback(_exit_wrapper, False)
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
@@ -612,10 +714,10 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|||||||
# Context may not be correct, so find the end of the chain
|
# Context may not be correct, so find the end of the chain
|
||||||
while 1:
|
while 1:
|
||||||
exc_context = new_exc.__context__
|
exc_context = new_exc.__context__
|
||||||
if exc_context is old_exc:
|
if exc_context is None or exc_context is old_exc:
|
||||||
# Context is already set correctly (see issue 20317)
|
# Context is already set correctly (see issue 20317)
|
||||||
return
|
return
|
||||||
if exc_context is None or exc_context is frame_exc:
|
if exc_context is frame_exc:
|
||||||
break
|
break
|
||||||
new_exc = exc_context
|
new_exc = exc_context
|
||||||
# Change the end of the chain to point to the exception
|
# Change the end of the chain to point to the exception
|
||||||
@@ -656,7 +758,7 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|||||||
return received_exc and suppressed_exc
|
return received_exc and suppressed_exc
|
||||||
|
|
||||||
|
|
||||||
class nullcontext(AbstractContextManager):
|
class nullcontext(AbstractContextManager, AbstractAsyncContextManager):
|
||||||
"""Context manager that does no additional processing.
|
"""Context manager that does no additional processing.
|
||||||
|
|
||||||
Used as a stand-in for a normal context manager, when a particular
|
Used as a stand-in for a normal context manager, when a particular
|
||||||
@@ -675,3 +777,24 @@ class nullcontext(AbstractContextManager):
|
|||||||
|
|
||||||
def __exit__(self, *excinfo):
|
def __exit__(self, *excinfo):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self.enter_result
|
||||||
|
|
||||||
|
async def __aexit__(self, *excinfo):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class chdir(AbstractContextManager):
|
||||||
|
"""Non thread-safe context manager to change the current working directory."""
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
self._old_cwd = []
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._old_cwd.append(os.getcwd())
|
||||||
|
os.chdir(self.path)
|
||||||
|
|
||||||
|
def __exit__(self, *excinfo):
|
||||||
|
os.chdir(self._old_cwd.pop())
|
||||||
|
|||||||
35
Lib/copy.py
vendored
35
Lib/copy.py
vendored
@@ -39,8 +39,8 @@ Python's deep copy operation avoids these problems by:
|
|||||||
set of components copied
|
set of components copied
|
||||||
|
|
||||||
This version does not copy types like module, class, function, method,
|
This version does not copy types like module, class, function, method,
|
||||||
nor stack trace, stack frame, nor file, socket, window, nor array, nor
|
nor stack trace, stack frame, nor file, socket, window, nor any
|
||||||
any similar types.
|
similar types.
|
||||||
|
|
||||||
Classes can use the same interfaces to control copying that they use
|
Classes can use the same interfaces to control copying that they use
|
||||||
to control pickling: they can define methods called __getinitargs__(),
|
to control pickling: they can define methods called __getinitargs__(),
|
||||||
@@ -56,11 +56,6 @@ class Error(Exception):
|
|||||||
pass
|
pass
|
||||||
error = Error # backward compatibility
|
error = Error # backward compatibility
|
||||||
|
|
||||||
try:
|
|
||||||
from org.python.core import PyStringMap
|
|
||||||
except ImportError:
|
|
||||||
PyStringMap = None
|
|
||||||
|
|
||||||
__all__ = ["Error", "copy", "deepcopy"]
|
__all__ = ["Error", "copy", "deepcopy"]
|
||||||
|
|
||||||
def copy(x):
|
def copy(x):
|
||||||
@@ -106,13 +101,11 @@ _copy_dispatch = d = {}
|
|||||||
|
|
||||||
def _copy_immutable(x):
|
def _copy_immutable(x):
|
||||||
return x
|
return x
|
||||||
for t in (type(None), int, float, bool, complex, str, tuple,
|
for t in (types.NoneType, int, float, bool, complex, str, tuple,
|
||||||
bytes, frozenset, type, range, slice, property,
|
bytes, frozenset, type, range, slice, property,
|
||||||
types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
|
types.BuiltinFunctionType, types.EllipsisType,
|
||||||
types.FunctionType, weakref.ref):
|
types.NotImplementedType, types.FunctionType, types.CodeType,
|
||||||
d[t] = _copy_immutable
|
weakref.ref):
|
||||||
t = getattr(types, "CodeType", None)
|
|
||||||
if t is not None:
|
|
||||||
d[t] = _copy_immutable
|
d[t] = _copy_immutable
|
||||||
|
|
||||||
d[list] = list.copy
|
d[list] = list.copy
|
||||||
@@ -120,9 +113,6 @@ d[dict] = dict.copy
|
|||||||
d[set] = set.copy
|
d[set] = set.copy
|
||||||
d[bytearray] = bytearray.copy
|
d[bytearray] = bytearray.copy
|
||||||
|
|
||||||
if PyStringMap is not None:
|
|
||||||
d[PyStringMap] = PyStringMap.copy
|
|
||||||
|
|
||||||
del d, t
|
del d, t
|
||||||
|
|
||||||
def deepcopy(x, memo=None, _nil=[]):
|
def deepcopy(x, memo=None, _nil=[]):
|
||||||
@@ -181,9 +171,9 @@ _deepcopy_dispatch = d = {}
|
|||||||
|
|
||||||
def _deepcopy_atomic(x, memo):
|
def _deepcopy_atomic(x, memo):
|
||||||
return x
|
return x
|
||||||
d[type(None)] = _deepcopy_atomic
|
d[types.NoneType] = _deepcopy_atomic
|
||||||
d[type(Ellipsis)] = _deepcopy_atomic
|
d[types.EllipsisType] = _deepcopy_atomic
|
||||||
d[type(NotImplemented)] = _deepcopy_atomic
|
d[types.NotImplementedType] = _deepcopy_atomic
|
||||||
d[int] = _deepcopy_atomic
|
d[int] = _deepcopy_atomic
|
||||||
d[float] = _deepcopy_atomic
|
d[float] = _deepcopy_atomic
|
||||||
d[bool] = _deepcopy_atomic
|
d[bool] = _deepcopy_atomic
|
||||||
@@ -192,6 +182,7 @@ d[bytes] = _deepcopy_atomic
|
|||||||
d[str] = _deepcopy_atomic
|
d[str] = _deepcopy_atomic
|
||||||
d[types.CodeType] = _deepcopy_atomic
|
d[types.CodeType] = _deepcopy_atomic
|
||||||
d[type] = _deepcopy_atomic
|
d[type] = _deepcopy_atomic
|
||||||
|
d[range] = _deepcopy_atomic
|
||||||
d[types.BuiltinFunctionType] = _deepcopy_atomic
|
d[types.BuiltinFunctionType] = _deepcopy_atomic
|
||||||
d[types.FunctionType] = _deepcopy_atomic
|
d[types.FunctionType] = _deepcopy_atomic
|
||||||
d[weakref.ref] = _deepcopy_atomic
|
d[weakref.ref] = _deepcopy_atomic
|
||||||
@@ -230,8 +221,6 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy):
|
|||||||
y[deepcopy(key, memo)] = deepcopy(value, memo)
|
y[deepcopy(key, memo)] = deepcopy(value, memo)
|
||||||
return y
|
return y
|
||||||
d[dict] = _deepcopy_dict
|
d[dict] = _deepcopy_dict
|
||||||
if PyStringMap is not None:
|
|
||||||
d[PyStringMap] = _deepcopy_dict
|
|
||||||
|
|
||||||
def _deepcopy_method(x, memo): # Copy instance methods
|
def _deepcopy_method(x, memo): # Copy instance methods
|
||||||
return type(x)(x.__func__, deepcopy(x.__self__, memo))
|
return type(x)(x.__func__, deepcopy(x.__self__, memo))
|
||||||
@@ -257,7 +246,7 @@ def _keep_alive(x, memo):
|
|||||||
|
|
||||||
def _reconstruct(x, memo, func, args,
|
def _reconstruct(x, memo, func, args,
|
||||||
state=None, listiter=None, dictiter=None,
|
state=None, listiter=None, dictiter=None,
|
||||||
deepcopy=deepcopy):
|
*, deepcopy=deepcopy):
|
||||||
deep = memo is not None
|
deep = memo is not None
|
||||||
if deep and args:
|
if deep and args:
|
||||||
args = (deepcopy(arg, memo) for arg in args)
|
args = (deepcopy(arg, memo) for arg in args)
|
||||||
@@ -300,4 +289,4 @@ def _reconstruct(x, memo, func, args,
|
|||||||
y[key] = value
|
y[key] = value
|
||||||
return y
|
return y
|
||||||
|
|
||||||
del types, weakref, PyStringMap
|
del types, weakref
|
||||||
|
|||||||
24
Lib/copyreg.py
vendored
24
Lib/copyreg.py
vendored
@@ -25,16 +25,16 @@ def constructor(object):
|
|||||||
|
|
||||||
# Example: provide pickling support for complex numbers.
|
# Example: provide pickling support for complex numbers.
|
||||||
|
|
||||||
try:
|
def pickle_complex(c):
|
||||||
complex
|
return complex, (c.real, c.imag)
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
|
|
||||||
def pickle_complex(c):
|
pickle(complex, pickle_complex, complex)
|
||||||
return complex, (c.real, c.imag)
|
|
||||||
|
|
||||||
pickle(complex, pickle_complex, complex)
|
def pickle_union(obj):
|
||||||
|
import functools, operator
|
||||||
|
return functools.reduce, (operator.or_, obj.__args__)
|
||||||
|
|
||||||
|
pickle(type(int | str), pickle_union)
|
||||||
|
|
||||||
# Support for pickling new-style objects
|
# Support for pickling new-style objects
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ def _reconstructor(cls, base, state):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
_HEAPTYPE = 1<<9
|
_HEAPTYPE = 1<<9
|
||||||
|
_new_type = type(int.__new__)
|
||||||
|
|
||||||
# Python code for object.__reduce_ex__ for protocols 0 and 1
|
# Python code for object.__reduce_ex__ for protocols 0 and 1
|
||||||
|
|
||||||
@@ -57,6 +58,9 @@ def _reduce_ex(self, proto):
|
|||||||
for base in cls.__mro__:
|
for base in cls.__mro__:
|
||||||
if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE:
|
if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE:
|
||||||
break
|
break
|
||||||
|
new = base.__new__
|
||||||
|
if isinstance(new, _new_type) and new.__self__ is base:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
base = object # not really reachable
|
base = object # not really reachable
|
||||||
if base is object:
|
if base is object:
|
||||||
@@ -79,6 +83,10 @@ def _reduce_ex(self, proto):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
dict = None
|
dict = None
|
||||||
else:
|
else:
|
||||||
|
if (type(self).__getstate__ is object.__getstate__ and
|
||||||
|
getattr(self, "__slots__", None)):
|
||||||
|
raise TypeError("a class that defines __slots__ without "
|
||||||
|
"defining __getstate__ cannot be pickled")
|
||||||
dict = getstate()
|
dict = getstate()
|
||||||
if dict:
|
if dict:
|
||||||
return _reconstructor, args, dict
|
return _reconstructor, args, dict
|
||||||
|
|||||||
48
Lib/csv.py
vendored
48
Lib/csv.py
vendored
@@ -4,17 +4,22 @@ csv.py - read/write/investigate CSV files
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from _csv import Error, writer, reader, \
|
import types
|
||||||
|
from _csv import Error, __version__, writer, reader, register_dialect, \
|
||||||
|
unregister_dialect, get_dialect, list_dialects, \
|
||||||
|
field_size_limit, \
|
||||||
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
|
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
|
||||||
|
QUOTE_STRINGS, QUOTE_NOTNULL, \
|
||||||
__doc__
|
__doc__
|
||||||
|
from _csv import Dialect as _Dialect
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
__all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
|
__all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
|
||||||
|
"QUOTE_STRINGS", "QUOTE_NOTNULL",
|
||||||
"Error", "Dialect", "__doc__", "excel", "excel_tab",
|
"Error", "Dialect", "__doc__", "excel", "excel_tab",
|
||||||
"field_size_limit", "reader", "writer",
|
"field_size_limit", "reader", "writer",
|
||||||
"Sniffer",
|
"register_dialect", "get_dialect", "list_dialects", "Sniffer",
|
||||||
"unregister_dialect", "__version__", "DictReader", "DictWriter",
|
"unregister_dialect", "__version__", "DictReader", "DictWriter",
|
||||||
"unix_dialect"]
|
"unix_dialect"]
|
||||||
|
|
||||||
@@ -57,10 +62,12 @@ class excel(Dialect):
|
|||||||
skipinitialspace = False
|
skipinitialspace = False
|
||||||
lineterminator = '\r\n'
|
lineterminator = '\r\n'
|
||||||
quoting = QUOTE_MINIMAL
|
quoting = QUOTE_MINIMAL
|
||||||
|
register_dialect("excel", excel)
|
||||||
|
|
||||||
class excel_tab(excel):
|
class excel_tab(excel):
|
||||||
"""Describe the usual properties of Excel-generated TAB-delimited files."""
|
"""Describe the usual properties of Excel-generated TAB-delimited files."""
|
||||||
delimiter = '\t'
|
delimiter = '\t'
|
||||||
|
register_dialect("excel-tab", excel_tab)
|
||||||
|
|
||||||
class unix_dialect(Dialect):
|
class unix_dialect(Dialect):
|
||||||
"""Describe the usual properties of Unix-generated CSV files."""
|
"""Describe the usual properties of Unix-generated CSV files."""
|
||||||
@@ -70,11 +77,14 @@ class unix_dialect(Dialect):
|
|||||||
skipinitialspace = False
|
skipinitialspace = False
|
||||||
lineterminator = '\n'
|
lineterminator = '\n'
|
||||||
quoting = QUOTE_ALL
|
quoting = QUOTE_ALL
|
||||||
|
register_dialect("unix", unix_dialect)
|
||||||
|
|
||||||
|
|
||||||
class DictReader:
|
class DictReader:
|
||||||
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
|
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
|
||||||
dialect="excel", *args, **kwds):
|
dialect="excel", *args, **kwds):
|
||||||
|
if fieldnames is not None and iter(fieldnames) is fieldnames:
|
||||||
|
fieldnames = list(fieldnames)
|
||||||
self._fieldnames = fieldnames # list of keys for the dict
|
self._fieldnames = fieldnames # list of keys for the dict
|
||||||
self.restkey = restkey # key to catch long rows
|
self.restkey = restkey # key to catch long rows
|
||||||
self.restval = restval # default value for short rows
|
self.restval = restval # default value for short rows
|
||||||
@@ -111,7 +121,7 @@ class DictReader:
|
|||||||
# values
|
# values
|
||||||
while row == []:
|
while row == []:
|
||||||
row = next(self.reader)
|
row = next(self.reader)
|
||||||
d = OrderedDict(zip(self.fieldnames, row))
|
d = dict(zip(self.fieldnames, row))
|
||||||
lf = len(self.fieldnames)
|
lf = len(self.fieldnames)
|
||||||
lr = len(row)
|
lr = len(row)
|
||||||
if lf < lr:
|
if lf < lr:
|
||||||
@@ -121,13 +131,18 @@ class DictReader:
|
|||||||
d[key] = self.restval
|
d[key] = self.restval
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
__class_getitem__ = classmethod(types.GenericAlias)
|
||||||
|
|
||||||
|
|
||||||
class DictWriter:
|
class DictWriter:
|
||||||
def __init__(self, f, fieldnames, restval="", extrasaction="raise",
|
def __init__(self, f, fieldnames, restval="", extrasaction="raise",
|
||||||
dialect="excel", *args, **kwds):
|
dialect="excel", *args, **kwds):
|
||||||
|
if fieldnames is not None and iter(fieldnames) is fieldnames:
|
||||||
|
fieldnames = list(fieldnames)
|
||||||
self.fieldnames = fieldnames # list of keys for the dict
|
self.fieldnames = fieldnames # list of keys for the dict
|
||||||
self.restval = restval # for writing short dicts
|
self.restval = restval # for writing short dicts
|
||||||
if extrasaction.lower() not in ("raise", "ignore"):
|
extrasaction = extrasaction.lower()
|
||||||
|
if extrasaction not in ("raise", "ignore"):
|
||||||
raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'"
|
raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'"
|
||||||
% extrasaction)
|
% extrasaction)
|
||||||
self.extrasaction = extrasaction
|
self.extrasaction = extrasaction
|
||||||
@@ -135,7 +150,7 @@ class DictWriter:
|
|||||||
|
|
||||||
def writeheader(self):
|
def writeheader(self):
|
||||||
header = dict(zip(self.fieldnames, self.fieldnames))
|
header = dict(zip(self.fieldnames, self.fieldnames))
|
||||||
self.writerow(header)
|
return self.writerow(header)
|
||||||
|
|
||||||
def _dict_to_list(self, rowdict):
|
def _dict_to_list(self, rowdict):
|
||||||
if self.extrasaction == "raise":
|
if self.extrasaction == "raise":
|
||||||
@@ -151,11 +166,8 @@ class DictWriter:
|
|||||||
def writerows(self, rowdicts):
|
def writerows(self, rowdicts):
|
||||||
return self.writer.writerows(map(self._dict_to_list, rowdicts))
|
return self.writer.writerows(map(self._dict_to_list, rowdicts))
|
||||||
|
|
||||||
# Guard Sniffer's type checking against builds that exclude complex()
|
__class_getitem__ = classmethod(types.GenericAlias)
|
||||||
try:
|
|
||||||
complex
|
|
||||||
except NameError:
|
|
||||||
complex = float
|
|
||||||
|
|
||||||
class Sniffer:
|
class Sniffer:
|
||||||
'''
|
'''
|
||||||
@@ -404,14 +416,10 @@ class Sniffer:
|
|||||||
continue # skip rows that have irregular number of columns
|
continue # skip rows that have irregular number of columns
|
||||||
|
|
||||||
for col in list(columnTypes.keys()):
|
for col in list(columnTypes.keys()):
|
||||||
|
thisType = complex
|
||||||
for thisType in [int, float, complex]:
|
try:
|
||||||
try:
|
thisType(row[col])
|
||||||
thisType(row[col])
|
except (ValueError, OverflowError):
|
||||||
break
|
|
||||||
except (ValueError, OverflowError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# fallback to length of string
|
# fallback to length of string
|
||||||
thisType = len(row[col])
|
thisType = len(row[col])
|
||||||
|
|
||||||
@@ -427,7 +435,7 @@ class Sniffer:
|
|||||||
# on whether it's a header
|
# on whether it's a header
|
||||||
hasHeader = 0
|
hasHeader = 0
|
||||||
for col, colType in columnTypes.items():
|
for col, colType in columnTypes.items():
|
||||||
if type(colType) == type(0): # it's a length
|
if isinstance(colType, int): # it's a length
|
||||||
if len(header[col]) != colType:
|
if len(header[col]) != colType:
|
||||||
hasHeader += 1
|
hasHeader += 1
|
||||||
else:
|
else:
|
||||||
|
|||||||
577
Lib/ctypes/__init__.py
vendored
Normal file
577
Lib/ctypes/__init__.py
vendored
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
"""create and manipulate C data types in Python"""
|
||||||
|
|
||||||
|
import os as _os, sys as _sys
|
||||||
|
import types as _types
|
||||||
|
|
||||||
|
__version__ = "1.1.0"
|
||||||
|
|
||||||
|
from _ctypes import Union, Structure, Array
|
||||||
|
from _ctypes import _Pointer
|
||||||
|
from _ctypes import CFuncPtr as _CFuncPtr
|
||||||
|
from _ctypes import __version__ as _ctypes_version
|
||||||
|
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
|
||||||
|
from _ctypes import ArgumentError
|
||||||
|
from _ctypes import SIZEOF_TIME_T
|
||||||
|
|
||||||
|
from struct import calcsize as _calcsize
|
||||||
|
|
||||||
|
if __version__ != _ctypes_version:
|
||||||
|
raise Exception("Version number mismatch", __version__, _ctypes_version)
|
||||||
|
|
||||||
|
if _os.name == "nt":
|
||||||
|
from _ctypes import FormatError
|
||||||
|
|
||||||
|
DEFAULT_MODE = RTLD_LOCAL
|
||||||
|
if _os.name == "posix" and _sys.platform == "darwin":
|
||||||
|
# On OS X 10.3, we use RTLD_GLOBAL as default mode
|
||||||
|
# because RTLD_LOCAL does not work at least on some
|
||||||
|
# libraries. OS X 10.3 is Darwin 7, so we check for
|
||||||
|
# that.
|
||||||
|
|
||||||
|
if int(_os.uname().release.split('.')[0]) < 8:
|
||||||
|
DEFAULT_MODE = RTLD_GLOBAL
|
||||||
|
|
||||||
|
from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
|
||||||
|
FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \
|
||||||
|
FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
|
||||||
|
FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR
|
||||||
|
|
||||||
|
# WINOLEAPI -> HRESULT
|
||||||
|
# WINOLEAPI_(type)
|
||||||
|
#
|
||||||
|
# STDMETHODCALLTYPE
|
||||||
|
#
|
||||||
|
# STDMETHOD(name)
|
||||||
|
# STDMETHOD_(type, name)
|
||||||
|
#
|
||||||
|
# STDAPICALLTYPE
|
||||||
|
|
||||||
|
def create_string_buffer(init, size=None):
|
||||||
|
"""create_string_buffer(aBytes) -> character array
|
||||||
|
create_string_buffer(anInteger) -> character array
|
||||||
|
create_string_buffer(aBytes, anInteger) -> character array
|
||||||
|
"""
|
||||||
|
if isinstance(init, bytes):
|
||||||
|
if size is None:
|
||||||
|
size = len(init)+1
|
||||||
|
_sys.audit("ctypes.create_string_buffer", init, size)
|
||||||
|
buftype = c_char * size
|
||||||
|
buf = buftype()
|
||||||
|
buf.value = init
|
||||||
|
return buf
|
||||||
|
elif isinstance(init, int):
|
||||||
|
_sys.audit("ctypes.create_string_buffer", None, init)
|
||||||
|
buftype = c_char * init
|
||||||
|
buf = buftype()
|
||||||
|
return buf
|
||||||
|
raise TypeError(init)
|
||||||
|
|
||||||
|
# Alias to create_string_buffer() for backward compatibility
|
||||||
|
c_buffer = create_string_buffer
|
||||||
|
|
||||||
|
_c_functype_cache = {}
|
||||||
|
def CFUNCTYPE(restype, *argtypes, **kw):
|
||||||
|
"""CFUNCTYPE(restype, *argtypes,
|
||||||
|
use_errno=False, use_last_error=False) -> function prototype.
|
||||||
|
|
||||||
|
restype: the result type
|
||||||
|
argtypes: a sequence specifying the argument types
|
||||||
|
|
||||||
|
The function prototype can be called in different ways to create a
|
||||||
|
callable object:
|
||||||
|
|
||||||
|
prototype(integer address) -> foreign function
|
||||||
|
prototype(callable) -> create and return a C callable function from callable
|
||||||
|
prototype(integer index, method name[, paramflags]) -> foreign function calling a COM method
|
||||||
|
prototype((ordinal number, dll object)[, paramflags]) -> foreign function exported by ordinal
|
||||||
|
prototype((function name, dll object)[, paramflags]) -> foreign function exported by name
|
||||||
|
"""
|
||||||
|
flags = _FUNCFLAG_CDECL
|
||||||
|
if kw.pop("use_errno", False):
|
||||||
|
flags |= _FUNCFLAG_USE_ERRNO
|
||||||
|
if kw.pop("use_last_error", False):
|
||||||
|
flags |= _FUNCFLAG_USE_LASTERROR
|
||||||
|
if kw:
|
||||||
|
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
|
||||||
|
|
||||||
|
try:
|
||||||
|
return _c_functype_cache[(restype, argtypes, flags)]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CFunctionType(_CFuncPtr):
|
||||||
|
_argtypes_ = argtypes
|
||||||
|
_restype_ = restype
|
||||||
|
_flags_ = flags
|
||||||
|
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
|
||||||
|
return CFunctionType
|
||||||
|
|
||||||
|
if _os.name == "nt":
|
||||||
|
from _ctypes import LoadLibrary as _dlopen
|
||||||
|
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
|
||||||
|
|
||||||
|
_win_functype_cache = {}
|
||||||
|
def WINFUNCTYPE(restype, *argtypes, **kw):
|
||||||
|
# docstring set later (very similar to CFUNCTYPE.__doc__)
|
||||||
|
flags = _FUNCFLAG_STDCALL
|
||||||
|
if kw.pop("use_errno", False):
|
||||||
|
flags |= _FUNCFLAG_USE_ERRNO
|
||||||
|
if kw.pop("use_last_error", False):
|
||||||
|
flags |= _FUNCFLAG_USE_LASTERROR
|
||||||
|
if kw:
|
||||||
|
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
|
||||||
|
|
||||||
|
try:
|
||||||
|
return _win_functype_cache[(restype, argtypes, flags)]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class WinFunctionType(_CFuncPtr):
|
||||||
|
_argtypes_ = argtypes
|
||||||
|
_restype_ = restype
|
||||||
|
_flags_ = flags
|
||||||
|
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
|
||||||
|
return WinFunctionType
|
||||||
|
if WINFUNCTYPE.__doc__:
|
||||||
|
WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")
|
||||||
|
|
||||||
|
elif _os.name == "posix":
|
||||||
|
from _ctypes import dlopen as _dlopen
|
||||||
|
|
||||||
|
from _ctypes import sizeof, byref, addressof, alignment, resize
|
||||||
|
from _ctypes import get_errno, set_errno
|
||||||
|
from _ctypes import _SimpleCData
|
||||||
|
|
||||||
|
def _check_size(typ, typecode=None):
|
||||||
|
# Check if sizeof(ctypes_type) against struct.calcsize. This
|
||||||
|
# should protect somewhat against a misconfigured libffi.
|
||||||
|
from struct import calcsize
|
||||||
|
if typecode is None:
|
||||||
|
# Most _type_ codes are the same as used in struct
|
||||||
|
typecode = typ._type_
|
||||||
|
actual, required = sizeof(typ), calcsize(typecode)
|
||||||
|
if actual != required:
|
||||||
|
raise SystemError("sizeof(%s) wrong: %d instead of %d" % \
|
||||||
|
(typ, actual, required))
|
||||||
|
|
||||||
|
class py_object(_SimpleCData):
|
||||||
|
_type_ = "O"
|
||||||
|
def __repr__(self):
|
||||||
|
try:
|
||||||
|
return super().__repr__()
|
||||||
|
except ValueError:
|
||||||
|
return "%s(<NULL>)" % type(self).__name__
|
||||||
|
_check_size(py_object, "P")
|
||||||
|
|
||||||
|
class c_short(_SimpleCData):
|
||||||
|
_type_ = "h"
|
||||||
|
_check_size(c_short)
|
||||||
|
|
||||||
|
class c_ushort(_SimpleCData):
|
||||||
|
_type_ = "H"
|
||||||
|
_check_size(c_ushort)
|
||||||
|
|
||||||
|
class c_long(_SimpleCData):
|
||||||
|
_type_ = "l"
|
||||||
|
_check_size(c_long)
|
||||||
|
|
||||||
|
class c_ulong(_SimpleCData):
|
||||||
|
_type_ = "L"
|
||||||
|
_check_size(c_ulong)
|
||||||
|
|
||||||
|
if _calcsize("i") == _calcsize("l"):
|
||||||
|
# if int and long have the same size, make c_int an alias for c_long
|
||||||
|
c_int = c_long
|
||||||
|
c_uint = c_ulong
|
||||||
|
else:
|
||||||
|
class c_int(_SimpleCData):
|
||||||
|
_type_ = "i"
|
||||||
|
_check_size(c_int)
|
||||||
|
|
||||||
|
class c_uint(_SimpleCData):
|
||||||
|
_type_ = "I"
|
||||||
|
_check_size(c_uint)
|
||||||
|
|
||||||
|
class c_float(_SimpleCData):
|
||||||
|
_type_ = "f"
|
||||||
|
_check_size(c_float)
|
||||||
|
|
||||||
|
class c_double(_SimpleCData):
|
||||||
|
_type_ = "d"
|
||||||
|
_check_size(c_double)
|
||||||
|
|
||||||
|
class c_longdouble(_SimpleCData):
|
||||||
|
_type_ = "g"
|
||||||
|
if sizeof(c_longdouble) == sizeof(c_double):
|
||||||
|
c_longdouble = c_double
|
||||||
|
|
||||||
|
if _calcsize("l") == _calcsize("q"):
|
||||||
|
# if long and long long have the same size, make c_longlong an alias for c_long
|
||||||
|
c_longlong = c_long
|
||||||
|
c_ulonglong = c_ulong
|
||||||
|
else:
|
||||||
|
class c_longlong(_SimpleCData):
|
||||||
|
_type_ = "q"
|
||||||
|
_check_size(c_longlong)
|
||||||
|
|
||||||
|
class c_ulonglong(_SimpleCData):
|
||||||
|
_type_ = "Q"
|
||||||
|
## def from_param(cls, val):
|
||||||
|
## return ('d', float(val), val)
|
||||||
|
## from_param = classmethod(from_param)
|
||||||
|
_check_size(c_ulonglong)
|
||||||
|
|
||||||
|
class c_ubyte(_SimpleCData):
|
||||||
|
_type_ = "B"
|
||||||
|
c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte
|
||||||
|
# backward compatibility:
|
||||||
|
##c_uchar = c_ubyte
|
||||||
|
_check_size(c_ubyte)
|
||||||
|
|
||||||
|
class c_byte(_SimpleCData):
|
||||||
|
_type_ = "b"
|
||||||
|
c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte
|
||||||
|
_check_size(c_byte)
|
||||||
|
|
||||||
|
class c_char(_SimpleCData):
|
||||||
|
_type_ = "c"
|
||||||
|
c_char.__ctype_le__ = c_char.__ctype_be__ = c_char
|
||||||
|
_check_size(c_char)
|
||||||
|
|
||||||
|
class c_char_p(_SimpleCData):
|
||||||
|
_type_ = "z"
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
|
||||||
|
_check_size(c_char_p, "P")
|
||||||
|
|
||||||
|
class c_void_p(_SimpleCData):
|
||||||
|
_type_ = "P"
|
||||||
|
c_voidp = c_void_p # backwards compatibility (to a bug)
|
||||||
|
_check_size(c_void_p)
|
||||||
|
|
||||||
|
class c_bool(_SimpleCData):
|
||||||
|
_type_ = "?"
|
||||||
|
|
||||||
|
from _ctypes import POINTER, pointer, _pointer_type_cache
|
||||||
|
|
||||||
|
class c_wchar_p(_SimpleCData):
|
||||||
|
_type_ = "Z"
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value)
|
||||||
|
|
||||||
|
class c_wchar(_SimpleCData):
|
||||||
|
_type_ = "u"
|
||||||
|
|
||||||
|
def _reset_cache():
|
||||||
|
_pointer_type_cache.clear()
|
||||||
|
_c_functype_cache.clear()
|
||||||
|
if _os.name == "nt":
|
||||||
|
_win_functype_cache.clear()
|
||||||
|
# _SimpleCData.c_wchar_p_from_param
|
||||||
|
POINTER(c_wchar).from_param = c_wchar_p.from_param
|
||||||
|
# _SimpleCData.c_char_p_from_param
|
||||||
|
POINTER(c_char).from_param = c_char_p.from_param
|
||||||
|
_pointer_type_cache[None] = c_void_p
|
||||||
|
|
||||||
|
def create_unicode_buffer(init, size=None):
|
||||||
|
"""create_unicode_buffer(aString) -> character array
|
||||||
|
create_unicode_buffer(anInteger) -> character array
|
||||||
|
create_unicode_buffer(aString, anInteger) -> character array
|
||||||
|
"""
|
||||||
|
if isinstance(init, str):
|
||||||
|
if size is None:
|
||||||
|
if sizeof(c_wchar) == 2:
|
||||||
|
# UTF-16 requires a surrogate pair (2 wchar_t) for non-BMP
|
||||||
|
# characters (outside [U+0000; U+FFFF] range). +1 for trailing
|
||||||
|
# NUL character.
|
||||||
|
size = sum(2 if ord(c) > 0xFFFF else 1 for c in init) + 1
|
||||||
|
else:
|
||||||
|
# 32-bit wchar_t (1 wchar_t per Unicode character). +1 for
|
||||||
|
# trailing NUL character.
|
||||||
|
size = len(init) + 1
|
||||||
|
_sys.audit("ctypes.create_unicode_buffer", init, size)
|
||||||
|
buftype = c_wchar * size
|
||||||
|
buf = buftype()
|
||||||
|
buf.value = init
|
||||||
|
return buf
|
||||||
|
elif isinstance(init, int):
|
||||||
|
_sys.audit("ctypes.create_unicode_buffer", None, init)
|
||||||
|
buftype = c_wchar * init
|
||||||
|
buf = buftype()
|
||||||
|
return buf
|
||||||
|
raise TypeError(init)
|
||||||
|
|
||||||
|
|
||||||
|
# XXX Deprecated
|
||||||
|
def SetPointerType(pointer, cls):
|
||||||
|
if _pointer_type_cache.get(cls, None) is not None:
|
||||||
|
raise RuntimeError("This type already exists in the cache")
|
||||||
|
if id(pointer) not in _pointer_type_cache:
|
||||||
|
raise RuntimeError("What's this???")
|
||||||
|
pointer.set_type(cls)
|
||||||
|
_pointer_type_cache[cls] = pointer
|
||||||
|
del _pointer_type_cache[id(pointer)]
|
||||||
|
|
||||||
|
# XXX Deprecated
|
||||||
|
def ARRAY(typ, len):
|
||||||
|
return typ * len
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class CDLL(object):
|
||||||
|
"""An instance of this class represents a loaded dll/shared
|
||||||
|
library, exporting functions using the standard C calling
|
||||||
|
convention (named 'cdecl' on Windows).
|
||||||
|
|
||||||
|
The exported functions can be accessed as attributes, or by
|
||||||
|
indexing with the function name. Examples:
|
||||||
|
|
||||||
|
<obj>.qsort -> callable object
|
||||||
|
<obj>['qsort'] -> callable object
|
||||||
|
|
||||||
|
Calling the functions releases the Python GIL during the call and
|
||||||
|
reacquires it afterwards.
|
||||||
|
"""
|
||||||
|
_func_flags_ = _FUNCFLAG_CDECL
|
||||||
|
_func_restype_ = c_int
|
||||||
|
# default values for repr
|
||||||
|
_name = '<uninitialized>'
|
||||||
|
_handle = 0
|
||||||
|
_FuncPtr = None
|
||||||
|
|
||||||
|
def __init__(self, name, mode=DEFAULT_MODE, handle=None,
|
||||||
|
use_errno=False,
|
||||||
|
use_last_error=False,
|
||||||
|
winmode=None):
|
||||||
|
self._name = name
|
||||||
|
flags = self._func_flags_
|
||||||
|
if use_errno:
|
||||||
|
flags |= _FUNCFLAG_USE_ERRNO
|
||||||
|
if use_last_error:
|
||||||
|
flags |= _FUNCFLAG_USE_LASTERROR
|
||||||
|
if _sys.platform.startswith("aix"):
|
||||||
|
"""When the name contains ".a(" and ends with ")",
|
||||||
|
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
|
||||||
|
archive(member) syntax for dlopen(), and the mode is adjusted.
|
||||||
|
Otherwise, name is presented to dlopen() as a file argument.
|
||||||
|
"""
|
||||||
|
if name and name.endswith(")") and ".a(" in name:
|
||||||
|
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
|
||||||
|
if _os.name == "nt":
|
||||||
|
if winmode is not None:
|
||||||
|
mode = winmode
|
||||||
|
else:
|
||||||
|
import nt
|
||||||
|
mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
|
||||||
|
if '/' in name or '\\' in name:
|
||||||
|
self._name = nt._getfullpathname(self._name)
|
||||||
|
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
|
||||||
|
|
||||||
|
class _FuncPtr(_CFuncPtr):
|
||||||
|
_flags_ = flags
|
||||||
|
_restype_ = self._func_restype_
|
||||||
|
self._FuncPtr = _FuncPtr
|
||||||
|
|
||||||
|
if handle is None:
|
||||||
|
self._handle = _dlopen(self._name, mode)
|
||||||
|
else:
|
||||||
|
self._handle = handle
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s '%s', handle %x at %#x>" % \
|
||||||
|
(self.__class__.__name__, self._name,
|
||||||
|
(self._handle & (_sys.maxsize*2 + 1)),
|
||||||
|
id(self) & (_sys.maxsize*2 + 1))
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name.startswith('__') and name.endswith('__'):
|
||||||
|
raise AttributeError(name)
|
||||||
|
func = self.__getitem__(name)
|
||||||
|
setattr(self, name, func)
|
||||||
|
return func
|
||||||
|
|
||||||
|
def __getitem__(self, name_or_ordinal):
|
||||||
|
func = self._FuncPtr((name_or_ordinal, self))
|
||||||
|
if not isinstance(name_or_ordinal, int):
|
||||||
|
func.__name__ = name_or_ordinal
|
||||||
|
return func
|
||||||
|
|
||||||
|
class PyDLL(CDLL):
|
||||||
|
"""This class represents the Python library itself. It allows
|
||||||
|
accessing Python API functions. The GIL is not released, and
|
||||||
|
Python exceptions are handled correctly.
|
||||||
|
"""
|
||||||
|
_func_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
|
||||||
|
|
||||||
|
if _os.name == "nt":
|
||||||
|
|
||||||
|
class WinDLL(CDLL):
|
||||||
|
"""This class represents a dll exporting functions using the
|
||||||
|
Windows stdcall calling convention.
|
||||||
|
"""
|
||||||
|
_func_flags_ = _FUNCFLAG_STDCALL
|
||||||
|
|
||||||
|
# XXX Hm, what about HRESULT as normal parameter?
|
||||||
|
# Mustn't it derive from c_long then?
|
||||||
|
from _ctypes import _check_HRESULT, _SimpleCData
|
||||||
|
class HRESULT(_SimpleCData):
|
||||||
|
_type_ = "l"
|
||||||
|
# _check_retval_ is called with the function's result when it
|
||||||
|
# is used as restype. It checks for the FAILED bit, and
|
||||||
|
# raises an OSError if it is set.
|
||||||
|
#
|
||||||
|
# The _check_retval_ method is implemented in C, so that the
|
||||||
|
# method definition itself is not included in the traceback
|
||||||
|
# when it raises an error - that is what we want (and Python
|
||||||
|
# doesn't have a way to raise an exception in the caller's
|
||||||
|
# frame).
|
||||||
|
_check_retval_ = _check_HRESULT
|
||||||
|
|
||||||
|
class OleDLL(CDLL):
|
||||||
|
"""This class represents a dll exporting functions using the
|
||||||
|
Windows stdcall calling convention, and returning HRESULT.
|
||||||
|
HRESULT error values are automatically raised as OSError
|
||||||
|
exceptions.
|
||||||
|
"""
|
||||||
|
_func_flags_ = _FUNCFLAG_STDCALL
|
||||||
|
_func_restype_ = HRESULT
|
||||||
|
|
||||||
|
class LibraryLoader(object):
|
||||||
|
def __init__(self, dlltype):
|
||||||
|
self._dlltype = dlltype
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name[0] == '_':
|
||||||
|
raise AttributeError(name)
|
||||||
|
try:
|
||||||
|
dll = self._dlltype(name)
|
||||||
|
except OSError:
|
||||||
|
raise AttributeError(name)
|
||||||
|
setattr(self, name, dll)
|
||||||
|
return dll
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
return getattr(self, name)
|
||||||
|
|
||||||
|
def LoadLibrary(self, name):
|
||||||
|
return self._dlltype(name)
|
||||||
|
|
||||||
|
__class_getitem__ = classmethod(_types.GenericAlias)
|
||||||
|
|
||||||
|
cdll = LibraryLoader(CDLL)
|
||||||
|
pydll = LibraryLoader(PyDLL)
|
||||||
|
|
||||||
|
if _os.name == "nt":
|
||||||
|
pythonapi = PyDLL("python dll", None, _sys.dllhandle)
|
||||||
|
elif _sys.platform == "cygwin":
|
||||||
|
pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2])
|
||||||
|
else:
|
||||||
|
pythonapi = PyDLL(None)
|
||||||
|
|
||||||
|
|
||||||
|
if _os.name == "nt":
|
||||||
|
windll = LibraryLoader(WinDLL)
|
||||||
|
oledll = LibraryLoader(OleDLL)
|
||||||
|
|
||||||
|
GetLastError = windll.kernel32.GetLastError
|
||||||
|
from _ctypes import get_last_error, set_last_error
|
||||||
|
|
||||||
|
def WinError(code=None, descr=None):
|
||||||
|
if code is None:
|
||||||
|
code = GetLastError()
|
||||||
|
if descr is None:
|
||||||
|
descr = FormatError(code).strip()
|
||||||
|
return OSError(None, descr, None, code)
|
||||||
|
|
||||||
|
if sizeof(c_uint) == sizeof(c_void_p):
|
||||||
|
c_size_t = c_uint
|
||||||
|
c_ssize_t = c_int
|
||||||
|
elif sizeof(c_ulong) == sizeof(c_void_p):
|
||||||
|
c_size_t = c_ulong
|
||||||
|
c_ssize_t = c_long
|
||||||
|
elif sizeof(c_ulonglong) == sizeof(c_void_p):
|
||||||
|
c_size_t = c_ulonglong
|
||||||
|
c_ssize_t = c_longlong
|
||||||
|
|
||||||
|
# functions
|
||||||
|
|
||||||
|
from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr
|
||||||
|
|
||||||
|
## void *memmove(void *, const void *, size_t);
|
||||||
|
memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr)
|
||||||
|
|
||||||
|
## void *memset(void *, int, size_t)
|
||||||
|
memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr)
|
||||||
|
|
||||||
|
def PYFUNCTYPE(restype, *argtypes):
|
||||||
|
class CFunctionType(_CFuncPtr):
|
||||||
|
_argtypes_ = argtypes
|
||||||
|
_restype_ = restype
|
||||||
|
_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
|
||||||
|
return CFunctionType
|
||||||
|
|
||||||
|
_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr)
|
||||||
|
def cast(obj, typ):
|
||||||
|
return _cast(obj, obj, typ)
|
||||||
|
|
||||||
|
_string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr)
|
||||||
|
def string_at(ptr, size=-1):
|
||||||
|
"""string_at(addr[, size]) -> string
|
||||||
|
|
||||||
|
Return the string at addr."""
|
||||||
|
return _string_at(ptr, size)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from _ctypes import _wstring_at_addr
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr)
|
||||||
|
def wstring_at(ptr, size=-1):
|
||||||
|
"""wstring_at(addr[, size]) -> string
|
||||||
|
|
||||||
|
Return the string at addr."""
|
||||||
|
return _wstring_at(ptr, size)
|
||||||
|
|
||||||
|
|
||||||
|
if _os.name == "nt": # COM stuff
|
||||||
|
def DllGetClassObject(rclsid, riid, ppv):
|
||||||
|
try:
|
||||||
|
ccom = __import__("comtypes.server.inprocserver", globals(), locals(), ['*'])
|
||||||
|
except ImportError:
|
||||||
|
return -2147221231 # CLASS_E_CLASSNOTAVAILABLE
|
||||||
|
else:
|
||||||
|
return ccom.DllGetClassObject(rclsid, riid, ppv)
|
||||||
|
|
||||||
|
def DllCanUnloadNow():
|
||||||
|
try:
|
||||||
|
ccom = __import__("comtypes.server.inprocserver", globals(), locals(), ['*'])
|
||||||
|
except ImportError:
|
||||||
|
return 0 # S_OK
|
||||||
|
return ccom.DllCanUnloadNow()
|
||||||
|
|
||||||
|
from ctypes._endian import BigEndianStructure, LittleEndianStructure
|
||||||
|
from ctypes._endian import BigEndianUnion, LittleEndianUnion
|
||||||
|
|
||||||
|
# Fill in specifically-sized types
|
||||||
|
c_int8 = c_byte
|
||||||
|
c_uint8 = c_ubyte
|
||||||
|
for kind in [c_short, c_int, c_long, c_longlong]:
|
||||||
|
if sizeof(kind) == 2: c_int16 = kind
|
||||||
|
elif sizeof(kind) == 4: c_int32 = kind
|
||||||
|
elif sizeof(kind) == 8: c_int64 = kind
|
||||||
|
for kind in [c_ushort, c_uint, c_ulong, c_ulonglong]:
|
||||||
|
if sizeof(kind) == 2: c_uint16 = kind
|
||||||
|
elif sizeof(kind) == 4: c_uint32 = kind
|
||||||
|
elif sizeof(kind) == 8: c_uint64 = kind
|
||||||
|
del(kind)
|
||||||
|
|
||||||
|
if SIZEOF_TIME_T == 8:
|
||||||
|
c_time_t = c_int64
|
||||||
|
elif SIZEOF_TIME_T == 4:
|
||||||
|
c_time_t = c_int32
|
||||||
|
else:
|
||||||
|
raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}")
|
||||||
|
|
||||||
|
_reset_cache()
|
||||||
327
Lib/ctypes/_aix.py
vendored
Normal file
327
Lib/ctypes/_aix.py
vendored
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
"""
|
||||||
|
Lib/ctypes.util.find_library() support for AIX
|
||||||
|
Similar approach as done for Darwin support by using separate files
|
||||||
|
but unlike Darwin - no extension such as ctypes.macholib.*
|
||||||
|
|
||||||
|
dlopen() is an interface to AIX initAndLoad() - primary documentation at:
|
||||||
|
https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/dlopen.htm
|
||||||
|
https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.basetrf1/load.htm
|
||||||
|
|
||||||
|
AIX supports two styles for dlopen(): svr4 (System V Release 4) which is common on posix
|
||||||
|
platforms, but also a BSD style - aka SVR3.
|
||||||
|
|
||||||
|
From AIX 5.3 Difference Addendum (December 2004)
|
||||||
|
2.9 SVR4 linking affinity
|
||||||
|
Nowadays, there are two major object file formats used by the operating systems:
|
||||||
|
XCOFF: The COFF enhanced by IBM and others. The original COFF (Common
|
||||||
|
Object File Format) was the base of SVR3 and BSD 4.2 systems.
|
||||||
|
ELF: Executable and Linking Format that was developed by AT&T and is a
|
||||||
|
base for SVR4 UNIX.
|
||||||
|
|
||||||
|
While the shared library content is identical on AIX - one is located as a filepath name
|
||||||
|
(svr4 style) and the other is located as a member of an archive (and the archive
|
||||||
|
is located as a filepath name).
|
||||||
|
|
||||||
|
The key difference arises when supporting multiple abi formats (i.e., 32 and 64 bit).
|
||||||
|
For svr4 either only one ABI is supported, or there are two directories, or there
|
||||||
|
are different file names. The most common solution for multiple ABI is multiple
|
||||||
|
directories.
|
||||||
|
|
||||||
|
For the XCOFF (aka AIX) style - one directory (one archive file) is sufficient
|
||||||
|
as multiple shared libraries can be in the archive - even sharing the same name.
|
||||||
|
In documentation the archive is also referred to as the "base" and the shared
|
||||||
|
library object is referred to as the "member".
|
||||||
|
|
||||||
|
For dlopen() on AIX (read initAndLoad()) the calls are similar.
|
||||||
|
Default activity occurs when no path information is provided. When path
|
||||||
|
information is provided dlopen() does not search any other directories.
|
||||||
|
|
||||||
|
For SVR4 - the shared library name is the name of the file expected: libFOO.so
|
||||||
|
For AIX - the shared library is expressed as base(member). The search is for the
|
||||||
|
base (e.g., libFOO.a) and once the base is found the shared library - identified by
|
||||||
|
member (e.g., libFOO.so, or shr.o) is located and loaded.
|
||||||
|
|
||||||
|
The mode bit RTLD_MEMBER tells initAndLoad() that it needs to use the AIX (SVR3)
|
||||||
|
naming style.
|
||||||
|
"""
|
||||||
|
__author__ = "Michael Felt <aixtools@felt.demon.nl>"
|
||||||
|
|
||||||
|
import re
|
||||||
|
from os import environ, path
|
||||||
|
from sys import executable
|
||||||
|
from ctypes import c_void_p, sizeof
|
||||||
|
from subprocess import Popen, PIPE, DEVNULL
|
||||||
|
|
||||||
|
# Executable bit size - 32 or 64
|
||||||
|
# Used to filter the search in an archive by size, e.g., -X64
|
||||||
|
AIX_ABI = sizeof(c_void_p) * 8
|
||||||
|
|
||||||
|
|
||||||
|
from sys import maxsize
|
||||||
|
def _last_version(libnames, sep):
|
||||||
|
def _num_version(libname):
|
||||||
|
# "libxyz.so.MAJOR.MINOR" => [MAJOR, MINOR]
|
||||||
|
parts = libname.split(sep)
|
||||||
|
nums = []
|
||||||
|
try:
|
||||||
|
while parts:
|
||||||
|
nums.insert(0, int(parts.pop()))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return nums or [maxsize]
|
||||||
|
return max(reversed(libnames), key=_num_version)
|
||||||
|
|
||||||
|
def get_ld_header(p):
|
||||||
|
# "nested-function, but placed at module level
|
||||||
|
ld_header = None
|
||||||
|
for line in p.stdout:
|
||||||
|
if line.startswith(('/', './', '../')):
|
||||||
|
ld_header = line
|
||||||
|
elif "INDEX" in line:
|
||||||
|
return ld_header.rstrip('\n')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_ld_header_info(p):
|
||||||
|
# "nested-function, but placed at module level
|
||||||
|
# as an ld_header was found, return known paths, archives and members
|
||||||
|
# these lines start with a digit
|
||||||
|
info = []
|
||||||
|
for line in p.stdout:
|
||||||
|
if re.match("[0-9]", line):
|
||||||
|
info.append(line)
|
||||||
|
else:
|
||||||
|
# blank line (separator), consume line and end for loop
|
||||||
|
break
|
||||||
|
return info
|
||||||
|
|
||||||
|
def get_ld_headers(file):
|
||||||
|
"""
|
||||||
|
Parse the header of the loader section of executable and archives
|
||||||
|
This function calls /usr/bin/dump -H as a subprocess
|
||||||
|
and returns a list of (ld_header, ld_header_info) tuples.
|
||||||
|
"""
|
||||||
|
# get_ld_headers parsing:
|
||||||
|
# 1. Find a line that starts with /, ./, or ../ - set as ld_header
|
||||||
|
# 2. If "INDEX" in occurs in a following line - return ld_header
|
||||||
|
# 3. get info (lines starting with [0-9])
|
||||||
|
ldr_headers = []
|
||||||
|
p = Popen(["/usr/bin/dump", f"-X{AIX_ABI}", "-H", file],
|
||||||
|
universal_newlines=True, stdout=PIPE, stderr=DEVNULL)
|
||||||
|
# be sure to read to the end-of-file - getting all entries
|
||||||
|
while ld_header := get_ld_header(p):
|
||||||
|
ldr_headers.append((ld_header, get_ld_header_info(p)))
|
||||||
|
p.stdout.close()
|
||||||
|
p.wait()
|
||||||
|
return ldr_headers
|
||||||
|
|
||||||
|
def get_shared(ld_headers):
|
||||||
|
"""
|
||||||
|
extract the shareable objects from ld_headers
|
||||||
|
character "[" is used to strip off the path information.
|
||||||
|
Note: the "[" and "]" characters that are part of dump -H output
|
||||||
|
are not removed here.
|
||||||
|
"""
|
||||||
|
shared = []
|
||||||
|
for (line, _) in ld_headers:
|
||||||
|
# potential member lines contain "["
|
||||||
|
# otherwise, no processing needed
|
||||||
|
if "[" in line:
|
||||||
|
# Strip off trailing colon (:)
|
||||||
|
shared.append(line[line.index("["):-1])
|
||||||
|
return shared
|
||||||
|
|
||||||
|
def get_one_match(expr, lines):
|
||||||
|
"""
|
||||||
|
Must be only one match, otherwise result is None.
|
||||||
|
When there is a match, strip leading "[" and trailing "]"
|
||||||
|
"""
|
||||||
|
# member names in the ld_headers output are between square brackets
|
||||||
|
expr = rf'\[({expr})\]'
|
||||||
|
matches = list(filter(None, (re.search(expr, line) for line in lines)))
|
||||||
|
if len(matches) == 1:
|
||||||
|
return matches[0].group(1)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# additional processing to deal with AIX legacy names for 64-bit members
|
||||||
|
def get_legacy(members):
|
||||||
|
"""
|
||||||
|
This routine provides historical aka legacy naming schemes started
|
||||||
|
in AIX4 shared library support for library members names.
|
||||||
|
e.g., in /usr/lib/libc.a the member name shr.o for 32-bit binary and
|
||||||
|
shr_64.o for 64-bit binary.
|
||||||
|
"""
|
||||||
|
if AIX_ABI == 64:
|
||||||
|
# AIX 64-bit member is one of shr64.o, shr_64.o, or shr4_64.o
|
||||||
|
expr = r'shr4?_?64\.o'
|
||||||
|
member = get_one_match(expr, members)
|
||||||
|
if member:
|
||||||
|
return member
|
||||||
|
else:
|
||||||
|
# 32-bit legacy names - both shr.o and shr4.o exist.
|
||||||
|
# shr.o is the preferred name so we look for shr.o first
|
||||||
|
# i.e., shr4.o is returned only when shr.o does not exist
|
||||||
|
for name in ['shr.o', 'shr4.o']:
|
||||||
|
member = get_one_match(re.escape(name), members)
|
||||||
|
if member:
|
||||||
|
return member
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_version(name, members):
|
||||||
|
"""
|
||||||
|
Sort list of members and return highest numbered version - if it exists.
|
||||||
|
This function is called when an unversioned libFOO.a(libFOO.so) has
|
||||||
|
not been found.
|
||||||
|
|
||||||
|
Versioning for the member name is expected to follow
|
||||||
|
GNU LIBTOOL conventions: the highest version (x, then X.y, then X.Y.z)
|
||||||
|
* find [libFoo.so.X]
|
||||||
|
* find [libFoo.so.X.Y]
|
||||||
|
* find [libFoo.so.X.Y.Z]
|
||||||
|
|
||||||
|
Before the GNU convention became the standard scheme regardless of
|
||||||
|
binary size AIX packagers used GNU convention "as-is" for 32-bit
|
||||||
|
archive members but used an "distinguishing" name for 64-bit members.
|
||||||
|
This scheme inserted either 64 or _64 between libFOO and .so
|
||||||
|
- generally libFOO_64.so, but occasionally libFOO64.so
|
||||||
|
"""
|
||||||
|
# the expression ending for versions must start as
|
||||||
|
# '.so.[0-9]', i.e., *.so.[at least one digit]
|
||||||
|
# while multiple, more specific expressions could be specified
|
||||||
|
# to search for .so.X, .so.X.Y and .so.X.Y.Z
|
||||||
|
# after the first required 'dot' digit
|
||||||
|
# any combination of additional 'dot' digits pairs are accepted
|
||||||
|
# anything more than libFOO.so.digits.digits.digits
|
||||||
|
# should be seen as a member name outside normal expectations
|
||||||
|
exprs = [rf'lib{name}\.so\.[0-9]+[0-9.]*',
|
||||||
|
rf'lib{name}_?64\.so\.[0-9]+[0-9.]*']
|
||||||
|
for expr in exprs:
|
||||||
|
versions = []
|
||||||
|
for line in members:
|
||||||
|
m = re.search(expr, line)
|
||||||
|
if m:
|
||||||
|
versions.append(m.group(0))
|
||||||
|
if versions:
|
||||||
|
return _last_version(versions, '.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_member(name, members):
|
||||||
|
"""
|
||||||
|
Return an archive member matching the request in name.
|
||||||
|
Name is the library name without any prefix like lib, suffix like .so,
|
||||||
|
or version number.
|
||||||
|
Given a list of members find and return the most appropriate result
|
||||||
|
Priority is given to generic libXXX.so, then a versioned libXXX.so.a.b.c
|
||||||
|
and finally, legacy AIX naming scheme.
|
||||||
|
"""
|
||||||
|
# look first for a generic match - prepend lib and append .so
|
||||||
|
expr = rf'lib{name}\.so'
|
||||||
|
member = get_one_match(expr, members)
|
||||||
|
if member:
|
||||||
|
return member
|
||||||
|
elif AIX_ABI == 64:
|
||||||
|
expr = rf'lib{name}64\.so'
|
||||||
|
member = get_one_match(expr, members)
|
||||||
|
if member:
|
||||||
|
return member
|
||||||
|
# since an exact match with .so as suffix was not found
|
||||||
|
# look for a versioned name
|
||||||
|
# If a versioned name is not found, look for AIX legacy member name
|
||||||
|
member = get_version(name, members)
|
||||||
|
if member:
|
||||||
|
return member
|
||||||
|
else:
|
||||||
|
return get_legacy(members)
|
||||||
|
|
||||||
|
def get_libpaths():
|
||||||
|
"""
|
||||||
|
On AIX, the buildtime searchpath is stored in the executable.
|
||||||
|
as "loader header information".
|
||||||
|
The command /usr/bin/dump -H extracts this info.
|
||||||
|
Prefix searched libraries with LD_LIBRARY_PATH (preferred),
|
||||||
|
or LIBPATH if defined. These paths are appended to the paths
|
||||||
|
to libraries the python executable is linked with.
|
||||||
|
This mimics AIX dlopen() behavior.
|
||||||
|
"""
|
||||||
|
libpaths = environ.get("LD_LIBRARY_PATH")
|
||||||
|
if libpaths is None:
|
||||||
|
libpaths = environ.get("LIBPATH")
|
||||||
|
if libpaths is None:
|
||||||
|
libpaths = []
|
||||||
|
else:
|
||||||
|
libpaths = libpaths.split(":")
|
||||||
|
objects = get_ld_headers(executable)
|
||||||
|
for (_, lines) in objects:
|
||||||
|
for line in lines:
|
||||||
|
# the second (optional) argument is PATH if it includes a /
|
||||||
|
path = line.split()[1]
|
||||||
|
if "/" in path:
|
||||||
|
libpaths.extend(path.split(":"))
|
||||||
|
return libpaths
|
||||||
|
|
||||||
|
def find_shared(paths, name):
|
||||||
|
"""
|
||||||
|
paths is a list of directories to search for an archive.
|
||||||
|
name is the abbreviated name given to find_library().
|
||||||
|
Process: search "paths" for archive, and if an archive is found
|
||||||
|
return the result of get_member().
|
||||||
|
If an archive is not found then return None
|
||||||
|
"""
|
||||||
|
for dir in paths:
|
||||||
|
# /lib is a symbolic link to /usr/lib, skip it
|
||||||
|
if dir == "/lib":
|
||||||
|
continue
|
||||||
|
# "lib" is prefixed to emulate compiler name resolution,
|
||||||
|
# e.g., -lc to libc
|
||||||
|
base = f'lib{name}.a'
|
||||||
|
archive = path.join(dir, base)
|
||||||
|
if path.exists(archive):
|
||||||
|
members = get_shared(get_ld_headers(archive))
|
||||||
|
member = get_member(re.escape(name), members)
|
||||||
|
if member is not None:
|
||||||
|
return (base, member)
|
||||||
|
else:
|
||||||
|
return (None, None)
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
def find_library(name):
|
||||||
|
"""AIX implementation of ctypes.util.find_library()
|
||||||
|
Find an archive member that will dlopen(). If not available,
|
||||||
|
also search for a file (or link) with a .so suffix.
|
||||||
|
|
||||||
|
AIX supports two types of schemes that can be used with dlopen().
|
||||||
|
The so-called SystemV Release4 (svr4) format is commonly suffixed
|
||||||
|
with .so while the (default) AIX scheme has the library (archive)
|
||||||
|
ending with the suffix .a
|
||||||
|
As an archive has multiple members (e.g., 32-bit and 64-bit) in one file
|
||||||
|
the argument passed to dlopen must include both the library and
|
||||||
|
the member names in a single string.
|
||||||
|
|
||||||
|
find_library() looks first for an archive (.a) with a suitable member.
|
||||||
|
If no archive+member pair is found, look for a .so file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
libpaths = get_libpaths()
|
||||||
|
(base, member) = find_shared(libpaths, name)
|
||||||
|
if base is not None:
|
||||||
|
return f"{base}({member})"
|
||||||
|
|
||||||
|
# To get here, a member in an archive has not been found
|
||||||
|
# In other words, either:
|
||||||
|
# a) a .a file was not found
|
||||||
|
# b) a .a file did not have a suitable member
|
||||||
|
# So, look for a .so file
|
||||||
|
# Check libpaths for .so file
|
||||||
|
# Note, the installation must prepare a link from a .so
|
||||||
|
# to a versioned file
|
||||||
|
# This is common practice by GNU libtool on other platforms
|
||||||
|
soname = f"lib{name}.so"
|
||||||
|
for dir in libpaths:
|
||||||
|
# /lib is a symbolic link to /usr/lib, skip it
|
||||||
|
if dir == "/lib":
|
||||||
|
continue
|
||||||
|
shlib = path.join(dir, soname)
|
||||||
|
if path.exists(shlib):
|
||||||
|
return soname
|
||||||
|
# if we are here, we have not found anything plausible
|
||||||
|
return None
|
||||||
78
Lib/ctypes/_endian.py
vendored
Normal file
78
Lib/ctypes/_endian.py
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import sys
|
||||||
|
from ctypes import *
|
||||||
|
|
||||||
|
_array_type = type(Array)
|
||||||
|
|
||||||
|
def _other_endian(typ):
|
||||||
|
"""Return the type with the 'other' byte order. Simple types like
|
||||||
|
c_int and so on already have __ctype_be__ and __ctype_le__
|
||||||
|
attributes which contain the types, for more complicated types
|
||||||
|
arrays and structures are supported.
|
||||||
|
"""
|
||||||
|
# check _OTHER_ENDIAN attribute (present if typ is primitive type)
|
||||||
|
if hasattr(typ, _OTHER_ENDIAN):
|
||||||
|
return getattr(typ, _OTHER_ENDIAN)
|
||||||
|
# if typ is array
|
||||||
|
if isinstance(typ, _array_type):
|
||||||
|
return _other_endian(typ._type_) * typ._length_
|
||||||
|
# if typ is structure
|
||||||
|
if issubclass(typ, Structure):
|
||||||
|
return typ
|
||||||
|
raise TypeError("This type does not support other endian: %s" % typ)
|
||||||
|
|
||||||
|
class _swapped_meta:
|
||||||
|
def __setattr__(self, attrname, value):
|
||||||
|
if attrname == "_fields_":
|
||||||
|
fields = []
|
||||||
|
for desc in value:
|
||||||
|
name = desc[0]
|
||||||
|
typ = desc[1]
|
||||||
|
rest = desc[2:]
|
||||||
|
fields.append((name, _other_endian(typ)) + rest)
|
||||||
|
value = fields
|
||||||
|
super().__setattr__(attrname, value)
|
||||||
|
class _swapped_struct_meta(_swapped_meta, type(Structure)): pass
|
||||||
|
class _swapped_union_meta(_swapped_meta, type(Union)): pass
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Note: The Structure metaclass checks for the *presence* (not the
|
||||||
|
# value!) of a _swapped_bytes_ attribute to determine the bit order in
|
||||||
|
# structures containing bit fields.
|
||||||
|
|
||||||
|
if sys.byteorder == "little":
|
||||||
|
_OTHER_ENDIAN = "__ctype_be__"
|
||||||
|
|
||||||
|
LittleEndianStructure = Structure
|
||||||
|
|
||||||
|
class BigEndianStructure(Structure, metaclass=_swapped_struct_meta):
|
||||||
|
"""Structure with big endian byte order"""
|
||||||
|
__slots__ = ()
|
||||||
|
_swappedbytes_ = None
|
||||||
|
|
||||||
|
LittleEndianUnion = Union
|
||||||
|
|
||||||
|
class BigEndianUnion(Union, metaclass=_swapped_union_meta):
|
||||||
|
"""Union with big endian byte order"""
|
||||||
|
__slots__ = ()
|
||||||
|
_swappedbytes_ = None
|
||||||
|
|
||||||
|
elif sys.byteorder == "big":
|
||||||
|
_OTHER_ENDIAN = "__ctype_le__"
|
||||||
|
|
||||||
|
BigEndianStructure = Structure
|
||||||
|
|
||||||
|
class LittleEndianStructure(Structure, metaclass=_swapped_struct_meta):
|
||||||
|
"""Structure with little endian byte order"""
|
||||||
|
__slots__ = ()
|
||||||
|
_swappedbytes_ = None
|
||||||
|
|
||||||
|
BigEndianUnion = Union
|
||||||
|
|
||||||
|
class LittleEndianUnion(Union, metaclass=_swapped_union_meta):
|
||||||
|
"""Union with little endian byte order"""
|
||||||
|
__slots__ = ()
|
||||||
|
_swappedbytes_ = None
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Invalid byteorder")
|
||||||
7
Lib/ctypes/macholib/README.ctypes
vendored
Normal file
7
Lib/ctypes/macholib/README.ctypes
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Files in this directory come from Bob Ippolito's py2app.
|
||||||
|
|
||||||
|
License: Any components of the py2app suite may be distributed under
|
||||||
|
the MIT or PSF open source licenses.
|
||||||
|
|
||||||
|
This is version 1.0, SVN revision 789, from 2006/01/25.
|
||||||
|
The main repository is http://svn.red-bean.com/bob/macholib/trunk/macholib/
|
||||||
9
Lib/ctypes/macholib/__init__.py
vendored
Normal file
9
Lib/ctypes/macholib/__init__.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
Enough Mach-O to make your head spin.
|
||||||
|
|
||||||
|
See the relevant header files in /usr/include/mach-o
|
||||||
|
|
||||||
|
And also Apple's documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = '1.0'
|
||||||
165
Lib/ctypes/macholib/dyld.py
vendored
Normal file
165
Lib/ctypes/macholib/dyld.py
vendored
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"""
|
||||||
|
dyld emulation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from ctypes.macholib.framework import framework_info
|
||||||
|
from ctypes.macholib.dylib import dylib_info
|
||||||
|
from itertools import *
|
||||||
|
try:
|
||||||
|
from _ctypes import _dyld_shared_cache_contains_path
|
||||||
|
except ImportError:
|
||||||
|
def _dyld_shared_cache_contains_path(*args):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'dyld_find', 'framework_find',
|
||||||
|
'framework_info', 'dylib_info',
|
||||||
|
]
|
||||||
|
|
||||||
|
# These are the defaults as per man dyld(1)
|
||||||
|
#
|
||||||
|
DEFAULT_FRAMEWORK_FALLBACK = [
|
||||||
|
os.path.expanduser("~/Library/Frameworks"),
|
||||||
|
"/Library/Frameworks",
|
||||||
|
"/Network/Library/Frameworks",
|
||||||
|
"/System/Library/Frameworks",
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFAULT_LIBRARY_FALLBACK = [
|
||||||
|
os.path.expanduser("~/lib"),
|
||||||
|
"/usr/local/lib",
|
||||||
|
"/lib",
|
||||||
|
"/usr/lib",
|
||||||
|
]
|
||||||
|
|
||||||
|
def dyld_env(env, var):
|
||||||
|
if env is None:
|
||||||
|
env = os.environ
|
||||||
|
rval = env.get(var)
|
||||||
|
if rval is None:
|
||||||
|
return []
|
||||||
|
return rval.split(':')
|
||||||
|
|
||||||
|
def dyld_image_suffix(env=None):
|
||||||
|
if env is None:
|
||||||
|
env = os.environ
|
||||||
|
return env.get('DYLD_IMAGE_SUFFIX')
|
||||||
|
|
||||||
|
def dyld_framework_path(env=None):
|
||||||
|
return dyld_env(env, 'DYLD_FRAMEWORK_PATH')
|
||||||
|
|
||||||
|
def dyld_library_path(env=None):
|
||||||
|
return dyld_env(env, 'DYLD_LIBRARY_PATH')
|
||||||
|
|
||||||
|
def dyld_fallback_framework_path(env=None):
|
||||||
|
return dyld_env(env, 'DYLD_FALLBACK_FRAMEWORK_PATH')
|
||||||
|
|
||||||
|
def dyld_fallback_library_path(env=None):
|
||||||
|
return dyld_env(env, 'DYLD_FALLBACK_LIBRARY_PATH')
|
||||||
|
|
||||||
|
def dyld_image_suffix_search(iterator, env=None):
|
||||||
|
"""For a potential path iterator, add DYLD_IMAGE_SUFFIX semantics"""
|
||||||
|
suffix = dyld_image_suffix(env)
|
||||||
|
if suffix is None:
|
||||||
|
return iterator
|
||||||
|
def _inject(iterator=iterator, suffix=suffix):
|
||||||
|
for path in iterator:
|
||||||
|
if path.endswith('.dylib'):
|
||||||
|
yield path[:-len('.dylib')] + suffix + '.dylib'
|
||||||
|
else:
|
||||||
|
yield path + suffix
|
||||||
|
yield path
|
||||||
|
return _inject()
|
||||||
|
|
||||||
|
def dyld_override_search(name, env=None):
|
||||||
|
# If DYLD_FRAMEWORK_PATH is set and this dylib_name is a
|
||||||
|
# framework name, use the first file that exists in the framework
|
||||||
|
# path if any. If there is none go on to search the DYLD_LIBRARY_PATH
|
||||||
|
# if any.
|
||||||
|
|
||||||
|
framework = framework_info(name)
|
||||||
|
|
||||||
|
if framework is not None:
|
||||||
|
for path in dyld_framework_path(env):
|
||||||
|
yield os.path.join(path, framework['name'])
|
||||||
|
|
||||||
|
# If DYLD_LIBRARY_PATH is set then use the first file that exists
|
||||||
|
# in the path. If none use the original name.
|
||||||
|
for path in dyld_library_path(env):
|
||||||
|
yield os.path.join(path, os.path.basename(name))
|
||||||
|
|
||||||
|
def dyld_executable_path_search(name, executable_path=None):
|
||||||
|
# If we haven't done any searching and found a library and the
|
||||||
|
# dylib_name starts with "@executable_path/" then construct the
|
||||||
|
# library name.
|
||||||
|
if name.startswith('@executable_path/') and executable_path is not None:
|
||||||
|
yield os.path.join(executable_path, name[len('@executable_path/'):])
|
||||||
|
|
||||||
|
def dyld_default_search(name, env=None):
|
||||||
|
yield name
|
||||||
|
|
||||||
|
framework = framework_info(name)
|
||||||
|
|
||||||
|
if framework is not None:
|
||||||
|
fallback_framework_path = dyld_fallback_framework_path(env)
|
||||||
|
for path in fallback_framework_path:
|
||||||
|
yield os.path.join(path, framework['name'])
|
||||||
|
|
||||||
|
fallback_library_path = dyld_fallback_library_path(env)
|
||||||
|
for path in fallback_library_path:
|
||||||
|
yield os.path.join(path, os.path.basename(name))
|
||||||
|
|
||||||
|
if framework is not None and not fallback_framework_path:
|
||||||
|
for path in DEFAULT_FRAMEWORK_FALLBACK:
|
||||||
|
yield os.path.join(path, framework['name'])
|
||||||
|
|
||||||
|
if not fallback_library_path:
|
||||||
|
for path in DEFAULT_LIBRARY_FALLBACK:
|
||||||
|
yield os.path.join(path, os.path.basename(name))
|
||||||
|
|
||||||
|
def dyld_find(name, executable_path=None, env=None):
|
||||||
|
"""
|
||||||
|
Find a library or framework using dyld semantics
|
||||||
|
"""
|
||||||
|
for path in dyld_image_suffix_search(chain(
|
||||||
|
dyld_override_search(name, env),
|
||||||
|
dyld_executable_path_search(name, executable_path),
|
||||||
|
dyld_default_search(name, env),
|
||||||
|
), env):
|
||||||
|
|
||||||
|
if os.path.isfile(path):
|
||||||
|
return path
|
||||||
|
try:
|
||||||
|
if _dyld_shared_cache_contains_path(path):
|
||||||
|
return path
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise ValueError("dylib %s could not be found" % (name,))
|
||||||
|
|
||||||
|
def framework_find(fn, executable_path=None, env=None):
|
||||||
|
"""
|
||||||
|
Find a framework using dyld semantics in a very loose manner.
|
||||||
|
|
||||||
|
Will take input such as:
|
||||||
|
Python
|
||||||
|
Python.framework
|
||||||
|
Python.framework/Versions/Current
|
||||||
|
"""
|
||||||
|
error = None
|
||||||
|
try:
|
||||||
|
return dyld_find(fn, executable_path=executable_path, env=env)
|
||||||
|
except ValueError as e:
|
||||||
|
error = e
|
||||||
|
fmwk_index = fn.rfind('.framework')
|
||||||
|
if fmwk_index == -1:
|
||||||
|
fmwk_index = len(fn)
|
||||||
|
fn += '.framework'
|
||||||
|
fn = os.path.join(fn, os.path.basename(fn[:fmwk_index]))
|
||||||
|
try:
|
||||||
|
return dyld_find(fn, executable_path=executable_path, env=env)
|
||||||
|
except ValueError:
|
||||||
|
raise error
|
||||||
|
finally:
|
||||||
|
error = None
|
||||||
42
Lib/ctypes/macholib/dylib.py
vendored
Normal file
42
Lib/ctypes/macholib/dylib.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Generic dylib path manipulation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
__all__ = ['dylib_info']
|
||||||
|
|
||||||
|
DYLIB_RE = re.compile(r"""(?x)
|
||||||
|
(?P<location>^.*)(?:^|/)
|
||||||
|
(?P<name>
|
||||||
|
(?P<shortname>\w+?)
|
||||||
|
(?:\.(?P<version>[^._]+))?
|
||||||
|
(?:_(?P<suffix>[^._]+))?
|
||||||
|
\.dylib$
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def dylib_info(filename):
|
||||||
|
"""
|
||||||
|
A dylib name can take one of the following four forms:
|
||||||
|
Location/Name.SomeVersion_Suffix.dylib
|
||||||
|
Location/Name.SomeVersion.dylib
|
||||||
|
Location/Name_Suffix.dylib
|
||||||
|
Location/Name.dylib
|
||||||
|
|
||||||
|
returns None if not found or a mapping equivalent to:
|
||||||
|
dict(
|
||||||
|
location='Location',
|
||||||
|
name='Name.SomeVersion_Suffix.dylib',
|
||||||
|
shortname='Name',
|
||||||
|
version='SomeVersion',
|
||||||
|
suffix='Suffix',
|
||||||
|
)
|
||||||
|
|
||||||
|
Note that SomeVersion and Suffix are optional and may be None
|
||||||
|
if not present.
|
||||||
|
"""
|
||||||
|
is_dylib = DYLIB_RE.match(filename)
|
||||||
|
if not is_dylib:
|
||||||
|
return None
|
||||||
|
return is_dylib.groupdict()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user