forked from Rust-related/RustPython
Compare commits
655 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 | ||
|
|
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 |
@@ -52,6 +52,7 @@
|
||||
"metas",
|
||||
"modpow",
|
||||
"nanos",
|
||||
"objclass",
|
||||
"peekable",
|
||||
"powc",
|
||||
"powf",
|
||||
@@ -171,6 +172,8 @@
|
||||
"scproxy",
|
||||
"setattro",
|
||||
"setcomp",
|
||||
"showwarnmsg",
|
||||
"warnmsg",
|
||||
"stacklevel",
|
||||
"subclasscheck",
|
||||
"subclasshook",
|
||||
|
||||
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"
|
||||
103
.github/workflows/ci.yaml
vendored
103
.github/workflows/ci.yaml
vendored
@@ -15,16 +15,16 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
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
|
||||
# Skip additional tests on Windows. They are checked on Linux and MacOS.
|
||||
WINDOWS_SKIPS: >-
|
||||
test_datetime
|
||||
test_glob
|
||||
test_importlib
|
||||
test_io
|
||||
test_os
|
||||
test_pathlib
|
||||
test_posixpath
|
||||
test_shutil
|
||||
test_venv
|
||||
# configparser: https://github.com/RustPython/RustPython/issues/4995#issuecomment-1582397417
|
||||
# socketserver: seems related to configparser crash.
|
||||
@@ -105,7 +105,7 @@ env:
|
||||
test_weakref
|
||||
test_yield_from
|
||||
# Python version targeted by the CI.
|
||||
PYTHON_VERSION: "3.11.4"
|
||||
PYTHON_VERSION: "3.12.3"
|
||||
|
||||
jobs:
|
||||
rust_tests:
|
||||
@@ -119,36 +119,41 @@ jobs:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Set up the Windows environment
|
||||
shell: bash
|
||||
run: |
|
||||
choco install llvm openssl --no-progress
|
||||
echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV
|
||||
cargo install --target-dir=target -v cargo-vcpkg
|
||||
cargo vcpkg -v build
|
||||
if: runner.os == 'Windows'
|
||||
- name: Set up the Mac environment
|
||||
run: brew install autoconf automake libtool
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: run clippy
|
||||
run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --exclude rustpython_wasm -- -Dwarnings
|
||||
|
||||
- name: run rust tests
|
||||
run: cargo test --workspace --exclude rustpython_wasm --verbose --features threading ${{ env.CARGO_ARGS }}
|
||||
if: runner.os != 'macOS'
|
||||
# temp skip ssl linking for Mac to avoid CI failure
|
||||
- name: run rust tests (MacOS no ssl)
|
||||
run: cargo test --workspace --exclude rustpython_wasm --verbose --no-default-features --features threading,stdlib,zlib,importlib,encodings,jit
|
||||
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
|
||||
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
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -171,7 +176,7 @@ jobs:
|
||||
name: Ensure compilation on various targets
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: i686-unknown-linux-gnu
|
||||
@@ -211,13 +216,6 @@ jobs:
|
||||
- name: Check compilation for 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
|
||||
with:
|
||||
target: x86_64-unknown-freebsd
|
||||
@@ -231,6 +229,7 @@ jobs:
|
||||
uses: coolreader18/redoxer-action@v1
|
||||
with:
|
||||
command: check
|
||||
args: --ignore-rust-version
|
||||
|
||||
snippets_cpython:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||
@@ -243,25 +242,28 @@ jobs:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Set up the Windows environment
|
||||
shell: bash
|
||||
run: |
|
||||
choco install llvm openssl --no-progress
|
||||
echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV
|
||||
cargo install cargo-vcpkg
|
||||
cargo vcpkg build
|
||||
if: runner.os == 'Windows'
|
||||
- name: Set up the Mac environment
|
||||
run: brew install autoconf automake libtool openssl@3
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: build rustpython
|
||||
run: cargo build --release --verbose --features=threading ${{ env.CARGO_ARGS }}
|
||||
- uses: actions/setup-python@v4
|
||||
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:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: run snippets
|
||||
@@ -276,16 +278,17 @@ jobs:
|
||||
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 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.MACOS_SKIPS }}
|
||||
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'
|
||||
name: run cpython platform-dependent tests (windows partial - fixme)
|
||||
run:
|
||||
target/release/rustpython -m test -j 1 -u all --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.WINDOWS_SKIPS }}
|
||||
target/release/rustpython -m test -j 1 --slowest --fail-env-changed -v -x ${{ env.PLATFORM_INDEPENDENT_TESTS }} ${{ env.WINDOWS_SKIPS }}
|
||||
- if: runner.os != 'Windows'
|
||||
name: check that --install-pip succeeds
|
||||
run: |
|
||||
mkdir site-packages
|
||||
target/release/rustpython --install-pip ensurepip --user
|
||||
target/release/rustpython -m pip install six
|
||||
- if: runner.os != 'Windows'
|
||||
name: Check that ensurepip succeeds.
|
||||
run: |
|
||||
@@ -303,19 +306,19 @@ jobs:
|
||||
name: Check Rust code with rustfmt and clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
- name: run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
run: cargo fmt --check
|
||||
- name: run clippy on wasm
|
||||
run: cargo clippy --manifest-path=wasm/lib/Cargo.toml -- -Dwarnings
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: install ruff
|
||||
run: python -m pip install ruff
|
||||
run: python -m pip install ruff==0.0.291 # astral-sh/ruff#7778
|
||||
- name: run python lint
|
||||
run: ruff extra_tests wasm examples --exclude='./.*',./Lib,./vm/Lib,./benches/ --select=E9,F63,F7,F82 --show-source
|
||||
- name: install prettier
|
||||
@@ -329,7 +332,7 @@ jobs:
|
||||
name: Run tests under miri
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly
|
||||
@@ -346,7 +349,7 @@ jobs:
|
||||
name: Check the WASM package and demo
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -354,15 +357,15 @@ jobs:
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: install geckodriver
|
||||
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
|
||||
tar -xzf geckodriver-v0.30.0-linux64.tar.gz -C geckodriver
|
||||
- uses: actions/setup-python@v4
|
||||
tar -xzf geckodriver-v0.34.0-linux64.tar.gz -C geckodriver
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- run: python -m pip install -r requirements.txt
|
||||
working-directory: ./wasm/tests
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
- name: run test
|
||||
run: |
|
||||
export PATH=$PATH:`pwd`/../../geckodriver
|
||||
@@ -371,6 +374,14 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
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
|
||||
if: github.ref == 'refs/heads/release'
|
||||
run: |
|
||||
@@ -382,7 +393,7 @@ jobs:
|
||||
working-directory: ./wasm/notebook
|
||||
- name: Deploy demo to Github Pages
|
||||
if: success() && github.ref == 'refs/heads/release'
|
||||
uses: peaceiris/actions-gh-pages@v2
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
env:
|
||||
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||
PUBLISH_DIR: ./wasm/demo/dist
|
||||
@@ -394,17 +405,19 @@ jobs:
|
||||
name: Run snippets and cpython tests on wasm-wasi
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: wasm32-wasi
|
||||
target: wasm32-wasip1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Setup Wasmer
|
||||
uses: wasmerio/setup-wasmer@v2
|
||||
uses: wasmerio/setup-wasmer@v3
|
||||
- name: Install clang
|
||||
run: sudo apt-get update && sudo apt-get install clang -y
|
||||
- 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
|
||||
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
|
||||
|
||||
18
.github/workflows/cron-ci.yaml
vendored
18
.github/workflows/cron-ci.yaml
vendored
@@ -7,7 +7,7 @@ name: Periodic checks/tasks
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,zlib,importlib,encodings,ssl,jit
|
||||
PYTHON_VERSION: "3.11.4"
|
||||
PYTHON_VERSION: "3.12.0"
|
||||
|
||||
jobs:
|
||||
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
name: Collect code coverage data
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Prepare code coverage data
|
||||
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./codecov.lcov
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
name: Collect regression test data
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: build rustpython
|
||||
run: cargo build --release --verbose
|
||||
@@ -71,9 +71,9 @@ jobs:
|
||||
name: Collect what is left data
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: build rustpython
|
||||
@@ -106,9 +106,9 @@ jobs:
|
||||
name: Collect benchmark data
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: cargo install cargo-criterion
|
||||
|
||||
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-*
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,6 +9,8 @@ __pycache__
|
||||
.vscode
|
||||
wasm-pack.log
|
||||
.idea/
|
||||
.envrc
|
||||
.python-version
|
||||
|
||||
flame-graph.html
|
||||
flame.txt
|
||||
|
||||
1766
Cargo.lock
generated
1766
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
212
Cargo.toml
212
Cargo.toml
@@ -1,83 +1,13 @@
|
||||
[package]
|
||||
name = "rustpython"
|
||||
version = "0.3.0"
|
||||
authors = ["RustPython Team"]
|
||||
edition = "2021"
|
||||
rust-version = "1.67.1"
|
||||
description = "A python interpreter written in rust."
|
||||
repository = "https://github.com/RustPython/RustPython"
|
||||
license = "MIT"
|
||||
include = ["LICENSE", "Cargo.toml", "src/**/*.rs"]
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"compiler", "compiler/core", "compiler/codegen",
|
||||
".", "common", "derive", "jit", "vm", "pylib", "stdlib", "wasm/lib", "derive-impl",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
rustpython-compiler-core = { path = "compiler/core", version = "0.3.0" }
|
||||
rustpython-compiler = { path = "compiler", version = "0.3.0" }
|
||||
rustpython-codegen = { path = "compiler/codegen", version = "0.3.0" }
|
||||
rustpython-common = { path = "common", version = "0.3.0" }
|
||||
rustpython-derive = { path = "derive", version = "0.3.0" }
|
||||
rustpython-derive-impl = { path = "derive-impl", version = "0.3.0" }
|
||||
rustpython-jit = { path = "jit", version = "0.3.0" }
|
||||
rustpython-vm = { path = "vm", version = "0.3.0" }
|
||||
rustpython-pylib = { path = "pylib", version = "0.3.0" }
|
||||
rustpython-stdlib = { path = "stdlib", version = "0.3.0" }
|
||||
rustpython-doc = { git = "https://github.com/RustPython/__doc__", tag = "0.3.0", version = "0.3.0" }
|
||||
|
||||
rustpython-literal = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
|
||||
rustpython-parser-core = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
|
||||
rustpython-ast = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
|
||||
rustpython-format = { git = "https://github.com/RustPython/Parser.git", tag = "0.3.0", version = "0.3.0" }
|
||||
# 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.7.6"
|
||||
anyhow = "1.0.45"
|
||||
ascii = "1.0"
|
||||
atty = "0.2.14"
|
||||
bitflags = "2.2.1"
|
||||
bstr = "0.2.17"
|
||||
cfg-if = "1.0"
|
||||
chrono = "0.4.19"
|
||||
crossbeam-utils = "0.8.16"
|
||||
flame = "0.2.2"
|
||||
glob = "0.3"
|
||||
hex = "0.4.3"
|
||||
indexmap = "1.8.1"
|
||||
insta = "1.14.0"
|
||||
itertools = "0.10.3"
|
||||
libc = "0.2.133"
|
||||
log = "0.4.16"
|
||||
nix = "0.26"
|
||||
malachite-bigint = { version = "0.1.0" }
|
||||
malachite-q = "0.3.2"
|
||||
malachite-base = "0.3.2"
|
||||
num-complex = "0.4.0"
|
||||
num-integer = "0.1.44"
|
||||
num-traits = "0.2"
|
||||
num_enum = "0.5.7"
|
||||
once_cell = "1.13"
|
||||
parking_lot = "0.12"
|
||||
paste = "1.0.7"
|
||||
rand = "0.8.5"
|
||||
rustyline = "11"
|
||||
serde = "1.0"
|
||||
schannel = "0.1.19"
|
||||
static_assertions = "1.1"
|
||||
syn = "1.0.91"
|
||||
thiserror = "1.0"
|
||||
thread_local = "1.1.4"
|
||||
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
|
||||
widestring = "0.5.1"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["threading", "stdlib", "zlib", "importlib"]
|
||||
@@ -85,22 +15,22 @@ importlib = ["rustpython-vm/importlib"]
|
||||
encodings = ["rustpython-vm/encodings"]
|
||||
stdlib = ["rustpython-stdlib", "rustpython-pylib", "encodings"]
|
||||
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"]
|
||||
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
||||
zlib = ["stdlib", "rustpython-stdlib/zlib"]
|
||||
bz2 = ["stdlib", "rustpython-stdlib/bz2"]
|
||||
sqlite = ["rustpython-stdlib/sqlite"]
|
||||
ssl = ["rustpython-stdlib/ssl"]
|
||||
ssl-vendor = ["rustpython-stdlib/ssl-vendor"]
|
||||
ssl-vendor = ["ssl", "rustpython-stdlib/ssl-vendor"]
|
||||
|
||||
[dependencies]
|
||||
rustpython-compiler = { workspace = true }
|
||||
rustpython-pylib = { workspace = true, optional = true }
|
||||
rustpython-stdlib = { workspace = true, optional = true }
|
||||
rustpython-vm = { workspace = true, default-features = false, features = ["compiler"] }
|
||||
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
|
||||
rustpython-vm = { workspace = true, features = ["compiler"] }
|
||||
rustpython-parser = { workspace = true }
|
||||
|
||||
atty = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
log = { workspace = true }
|
||||
flame = { workspace = true, optional = true }
|
||||
@@ -117,9 +47,8 @@ libc = { workspace = true }
|
||||
rustyline = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
cpython = "0.7.0"
|
||||
criterion = "0.3.5"
|
||||
python3-sys = "0.7.1"
|
||||
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||
pyo3 = { version = "0.22", features = ["auto-initialize"] }
|
||||
|
||||
[[bench]]
|
||||
name = "execution"
|
||||
@@ -151,5 +80,118 @@ lto = "thin"
|
||||
|
||||
[patch.crates-io]
|
||||
# REDOX START, Uncomment when you want to compile/check with redoxer
|
||||
# nix = { git = "https://github.com/coolreader18/nix", branch = "0.26.2-redox" }
|
||||
# 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"
|
||||
|
||||
@@ -25,7 +25,7 @@ RustPython requires the following:
|
||||
stable version: `rustup update stable`
|
||||
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
|
||||
do so.
|
||||
- CPython version 3.11 or higher
|
||||
- CPython version 3.12 or higher
|
||||
- CPython can be installed by your operating system's package manager,
|
||||
from the [Python website](https://www.python.org/downloads/), or
|
||||
using a third-party distribution, such as
|
||||
|
||||
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.
|
||||
|
||||
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,
|
||||
.getOptionalRelease() and .getMandatoryRelease().
|
||||
@@ -96,7 +96,7 @@ class _Feature:
|
||||
"""Return release in which this feature will become mandatory.
|
||||
|
||||
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
|
||||
|
||||
@@ -143,5 +143,5 @@ generator_stop = _Feature((3, 5, 0, "beta", 1),
|
||||
CO_FUTURE_GENERATOR_STOP)
|
||||
|
||||
annotations = _Feature((3, 7, 0, "beta", 1),
|
||||
(3, 11, 0, "alpha", 0),
|
||||
None,
|
||||
CO_FUTURE_ANNOTATIONS)
|
||||
|
||||
74
Lib/_collections_abc.py
vendored
74
Lib/_collections_abc.py
vendored
@@ -6,6 +6,32 @@
|
||||
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
|
||||
import sys
|
||||
|
||||
@@ -23,7 +49,7 @@ __all__ = ["Awaitable", "Coroutine",
|
||||
"Mapping", "MutableMapping",
|
||||
"MappingView", "KeysView", "ItemsView", "ValuesView",
|
||||
"Sequence", "MutableSequence",
|
||||
"ByteString",
|
||||
"ByteString", "Buffer",
|
||||
]
|
||||
|
||||
# This module has been renamed from collections.abc to _collections_abc to
|
||||
@@ -413,6 +439,21 @@ class Collection(Sized, Iterable, Container):
|
||||
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):
|
||||
""" Represent `Callable[argtypes, resulttype]`.
|
||||
|
||||
@@ -455,15 +496,8 @@ class _CallableGenericAlias(GenericAlias):
|
||||
# rather than the default types.GenericAlias object. Most of the
|
||||
# code is copied from typing's _GenericAlias and the builtin
|
||||
# types.GenericAlias.
|
||||
|
||||
if not isinstance(item, tuple):
|
||||
item = (item,)
|
||||
# A special case in PEP 612 where if X = Callable[P, int],
|
||||
# then X[int, str] == X[[int, str]].
|
||||
if (len(self.__parameters__) == 1
|
||||
and _is_param_expr(self.__parameters__[0])
|
||||
and item and not _is_param_expr(item[0])):
|
||||
item = (item,)
|
||||
|
||||
new_args = super().__getitem__(item).__args__
|
||||
|
||||
@@ -491,9 +525,8 @@ def _type_repr(obj):
|
||||
|
||||
Copied from :mod:`typing` since collections.abc
|
||||
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 obj.__module__ == 'builtins':
|
||||
return obj.__qualname__
|
||||
@@ -1038,8 +1071,27 @@ Sequence.register(str)
|
||||
Sequence.register(range)
|
||||
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.
|
||||
|
||||
XXX Should add all their methods.
|
||||
|
||||
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:
|
||||
from os import *
|
||||
except ImportError:
|
||||
import abc
|
||||
import abc, sys
|
||||
|
||||
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():
|
||||
import _dummy_os, sys
|
||||
sys.modules['os'] = _dummy_os
|
||||
sys.modules['os.path'] = _dummy_os.path
|
||||
sys.modules['os'] = sys.modules['posix'] = sys.modules[__name__]
|
||||
|
||||
import posixpath as path
|
||||
import sys
|
||||
sys.modules['os.path'] = path
|
||||
del sys
|
||||
|
||||
sep = path.sep
|
||||
supports_dir_fd = set()
|
||||
supports_effective_ids = set()
|
||||
supports_fd = set()
|
||||
supports_follow_symlinks = set()
|
||||
|
||||
|
||||
def fspath(path):
|
||||
|
||||
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
|
||||
return cls(f)
|
||||
if not isinstance(f, float):
|
||||
raise TypeError("argument must be int or float.")
|
||||
if _math.isinf(f) or _math.isnan(f):
|
||||
return cls(repr(f))
|
||||
if _math.copysign(1.0, f) == 1.0:
|
||||
sign = 0
|
||||
sign = 0 if f >= 0 else 1
|
||||
k = 0
|
||||
coeff = str(abs(f))
|
||||
elif isinstance(f, float):
|
||||
if _math.isinf(f) or _math.isnan(f):
|
||||
return cls(repr(f))
|
||||
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:
|
||||
sign = 1
|
||||
n, d = abs(f).as_integer_ratio()
|
||||
k = d.bit_length() - 1
|
||||
result = _dec_from_triple(sign, str(n*5**k), -k)
|
||||
raise TypeError("argument must be int or float.")
|
||||
|
||||
result = _dec_from_triple(sign, coeff, -k)
|
||||
if cls is Decimal:
|
||||
return result
|
||||
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.
|
||||
|
||||
When encoding is not None, just return it.
|
||||
Otherwise, return the default text encoding (i.e. "locale").
|
||||
When encoding is not None, this function returns it.
|
||||
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
|
||||
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.
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = "locale"
|
||||
if sys.flags.utf8_mode:
|
||||
encoding = "utf-8"
|
||||
else:
|
||||
encoding = "locale"
|
||||
if sys.flags.warn_default_encoding:
|
||||
import warnings
|
||||
warnings.warn("'encoding' argument not specified.",
|
||||
@@ -101,7 +105,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
'b' binary mode
|
||||
't' text mode (default)
|
||||
'+' 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
|
||||
@@ -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
|
||||
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.
|
||||
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
|
||||
@@ -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):
|
||||
raise TypeError("invalid errors: %r" % errors)
|
||||
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)
|
||||
creating = "x" in modes
|
||||
reading = "r" in modes
|
||||
@@ -215,13 +214,6 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
|
||||
updating = "+" in modes
|
||||
text = "t" 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:
|
||||
raise ValueError("can't have text and binary mode at once")
|
||||
if creating + reading + writing + appending > 1:
|
||||
@@ -311,22 +303,6 @@ except AttributeError:
|
||||
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
|
||||
# same object.
|
||||
try:
|
||||
@@ -338,8 +314,7 @@ except AttributeError:
|
||||
|
||||
class IOBase(metaclass=abc.ABCMeta):
|
||||
|
||||
"""The abstract base class for all I/O classes, acting on streams of
|
||||
bytes. There is no public constructor.
|
||||
"""The abstract base class for all I/O classes.
|
||||
|
||||
This class provides dummy implementations for many methods that
|
||||
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
|
||||
than self.buffer_size.
|
||||
"""
|
||||
self._checkClosed("peek of closed file")
|
||||
with self._read_lock:
|
||||
return self._peek_unlocked(size)
|
||||
|
||||
@@ -1172,6 +1148,7 @@ class BufferedReader(_BufferedIOMixin):
|
||||
"""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
|
||||
# only return buffered bytes. Otherwise, we do one raw read.
|
||||
self._checkClosed("read of closed file")
|
||||
if size < 0:
|
||||
size = self.buffer_size
|
||||
if size == 0:
|
||||
@@ -1189,6 +1166,8 @@ class BufferedReader(_BufferedIOMixin):
|
||||
def _readinto(self, buf, read1):
|
||||
"""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
|
||||
# we may not be able to assign bytes to it, and slicing it
|
||||
# would create a new object.
|
||||
@@ -1233,11 +1212,13 @@ class BufferedReader(_BufferedIOMixin):
|
||||
return written
|
||||
|
||||
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):
|
||||
if whence not in valid_seek_flags:
|
||||
raise ValueError("invalid whence value")
|
||||
self._checkClosed("seek of closed file")
|
||||
with self._read_lock:
|
||||
if whence == 1:
|
||||
pos -= len(self._read_buf) - self._read_pos
|
||||
@@ -1845,7 +1826,7 @@ class TextIOBase(IOBase):
|
||||
"""Base class for text I/O.
|
||||
|
||||
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):
|
||||
@@ -1997,7 +1978,7 @@ class TextIOWrapper(TextIOBase):
|
||||
r"""Character and line based layer over a BufferedIOBase object, buffer.
|
||||
|
||||
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
|
||||
codecs.register) and defaults to "strict".
|
||||
@@ -2031,19 +2012,7 @@ class TextIOWrapper(TextIOBase):
|
||||
encoding = text_encoding(encoding)
|
||||
|
||||
if encoding == "locale":
|
||||
try:
|
||||
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)
|
||||
encoding = self._get_locale_encoding()
|
||||
|
||||
if not isinstance(encoding, str):
|
||||
raise ValueError("invalid encoding: %r" % encoding)
|
||||
@@ -2176,6 +2145,8 @@ class TextIOWrapper(TextIOBase):
|
||||
else:
|
||||
if not isinstance(encoding, str):
|
||||
raise TypeError("invalid encoding: %r" % encoding)
|
||||
if encoding == "locale":
|
||||
encoding = self._get_locale_encoding()
|
||||
|
||||
if newline is Ellipsis:
|
||||
newline = self._readnl
|
||||
@@ -2243,8 +2214,9 @@ class TextIOWrapper(TextIOBase):
|
||||
self.buffer.write(b)
|
||||
if self._line_buffering and (haslf or "\r" in s):
|
||||
self.flush()
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
if self._snapshot is not None:
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
if self._decoder:
|
||||
self._decoder.reset()
|
||||
return length
|
||||
@@ -2280,6 +2252,15 @@ class TextIOWrapper(TextIOBase):
|
||||
self._decoded_chars_used += len(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):
|
||||
"""Rewind the _decoded_chars buffer."""
|
||||
if self._decoded_chars_used < n:
|
||||
@@ -2549,8 +2530,9 @@ class TextIOWrapper(TextIOBase):
|
||||
# Read everything.
|
||||
result = (self._get_decoded_chars() +
|
||||
decoder.decode(self.buffer.read(), final=True))
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
if self._snapshot is not None:
|
||||
self._set_decoded_chars('')
|
||||
self._snapshot = None
|
||||
return result
|
||||
else:
|
||||
# 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
|
||||
|
||||
|
||||
class Quitter(object):
|
||||
def __init__(self, name, eof):
|
||||
self.name = name
|
||||
@@ -48,7 +47,7 @@ class _Printer(object):
|
||||
data = None
|
||||
for filename in self.__filenames:
|
||||
try:
|
||||
with open(filename, "r") as fp:
|
||||
with open(filename, encoding='utf-8') as fp:
|
||||
data = fp.read()
|
||||
break
|
||||
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)
|
||||
2
Lib/abc.py
vendored
2
Lib/abc.py
vendored
@@ -18,7 +18,7 @@ def abstractmethod(funcobj):
|
||||
|
||||
class C(metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def my_abstract_method(self, ...):
|
||||
def my_abstract_method(self, arg1, arg2, argN):
|
||||
...
|
||||
"""
|
||||
funcobj.__isabstractmethod__ = True
|
||||
|
||||
64
Lib/argparse.py
vendored
64
Lib/argparse.py
vendored
@@ -345,21 +345,22 @@ class HelpFormatter(object):
|
||||
def get_lines(parts, indent, prefix=None):
|
||||
lines = []
|
||||
line = []
|
||||
indent_length = len(indent)
|
||||
if prefix is not None:
|
||||
line_len = len(prefix) - 1
|
||||
else:
|
||||
line_len = len(indent) - 1
|
||||
line_len = indent_length - 1
|
||||
for part in parts:
|
||||
if line_len + 1 + len(part) > text_width and line:
|
||||
lines.append(indent + ' '.join(line))
|
||||
line = []
|
||||
line_len = len(indent) - 1
|
||||
line_len = indent_length - 1
|
||||
line.append(part)
|
||||
line_len += len(part) + 1
|
||||
if line:
|
||||
lines.append(indent + ' '.join(line))
|
||||
if prefix is not None:
|
||||
lines[0] = lines[0][len(indent):]
|
||||
lines[0] = lines[0][indent_length:]
|
||||
return lines
|
||||
|
||||
# if prog is short, follow it with optionals or positionals
|
||||
@@ -403,10 +404,18 @@ class HelpFormatter(object):
|
||||
except ValueError:
|
||||
continue
|
||||
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:
|
||||
|
||||
suppressed_actions_count = 0
|
||||
for action in group._group_actions:
|
||||
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 start in inserts:
|
||||
inserts[start] += ' ['
|
||||
@@ -416,7 +425,7 @@ class HelpFormatter(object):
|
||||
inserts[end] += ']'
|
||||
else:
|
||||
inserts[end] = ']'
|
||||
else:
|
||||
elif exposed_actions_count > 1:
|
||||
if start in inserts:
|
||||
inserts[start] += ' ('
|
||||
else:
|
||||
@@ -490,7 +499,6 @@ class HelpFormatter(object):
|
||||
text = _re.sub(r'(%s) ' % open, 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'\(([^|]*)\)', r'\1', text)
|
||||
text = text.strip()
|
||||
|
||||
# return the text
|
||||
@@ -875,16 +883,19 @@ class Action(_AttributeHolder):
|
||||
raise NotImplementedError(_('.__call__() not defined'))
|
||||
|
||||
|
||||
# FIXME: remove together with `BooleanOptionalAction` deprecated arguments.
|
||||
_deprecated_default = object()
|
||||
|
||||
class BooleanOptionalAction(Action):
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest,
|
||||
default=None,
|
||||
type=None,
|
||||
choices=None,
|
||||
type=_deprecated_default,
|
||||
choices=_deprecated_default,
|
||||
required=False,
|
||||
help=None,
|
||||
metavar=None):
|
||||
metavar=_deprecated_default):
|
||||
|
||||
_option_strings = []
|
||||
for option_string in option_strings:
|
||||
@@ -894,6 +905,24 @@ class BooleanOptionalAction(Action):
|
||||
option_string = '--no-' + option_string[2:]
|
||||
_option_strings.append(option_string)
|
||||
|
||||
# We need `_deprecated` special value to ban explicit arguments that
|
||||
# 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__(
|
||||
option_strings=_option_strings,
|
||||
dest=dest,
|
||||
@@ -2165,7 +2194,9 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
# replace arguments referencing files with the file content
|
||||
else:
|
||||
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 = []
|
||||
for arg_line in args_file.read().splitlines():
|
||||
for arg in self.convert_arg_line_to_args(arg_line):
|
||||
@@ -2479,9 +2510,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
not action.option_strings):
|
||||
if action.default is not None:
|
||||
value = action.default
|
||||
self._check_value(action, value)
|
||||
else:
|
||||
# since arg_strings is always [] at this point
|
||||
# there is no need to use self._check_value(action, value)
|
||||
value = arg_strings
|
||||
self._check_value(action, value)
|
||||
|
||||
# single argument or optional argument produces a single value
|
||||
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
|
||||
@@ -2523,7 +2556,6 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
|
||||
# ArgumentTypeErrors indicate errors
|
||||
except ArgumentTypeError as err:
|
||||
name = getattr(action.type, '__name__', repr(action.type))
|
||||
msg = str(err)
|
||||
raise ArgumentError(action, msg)
|
||||
|
||||
@@ -2595,9 +2627,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
|
||||
def _print_message(self, message, file=None):
|
||||
if message:
|
||||
if file is None:
|
||||
file = _sys.stderr
|
||||
file.write(message)
|
||||
file = file or _sys.stderr
|
||||
try:
|
||||
file.write(message)
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
|
||||
# ===============
|
||||
# Exiting methods
|
||||
|
||||
350
Lib/ast.py
vendored
350
Lib/ast.py
vendored
@@ -25,9 +25,10 @@
|
||||
:license: Python License.
|
||||
"""
|
||||
import sys
|
||||
import re
|
||||
from _ast import *
|
||||
from contextlib import contextmanager, nullcontext
|
||||
from enum import IntEnum, auto
|
||||
from enum import IntEnum, auto, _simple_enum
|
||||
|
||||
|
||||
def parse(source, filename='<unknown>', mode='exec', *,
|
||||
@@ -40,12 +41,13 @@ def parse(source, filename='<unknown>', mode='exec', *,
|
||||
flags = PyCF_ONLY_AST
|
||||
if type_comments:
|
||||
flags |= PyCF_TYPE_COMMENTS
|
||||
if isinstance(feature_version, tuple):
|
||||
major, minor = feature_version # Should be a 2-tuple.
|
||||
assert major == 3
|
||||
feature_version = minor
|
||||
elif feature_version is None:
|
||||
if feature_version is None:
|
||||
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.
|
||||
return compile(source, filename, mode, flags,
|
||||
_feature_version=feature_version)
|
||||
@@ -292,9 +294,7 @@ def get_docstring(node, clean=True):
|
||||
if not(node.body and isinstance(node.body[0], Expr)):
|
||||
return None
|
||||
node = node.body[0].value
|
||||
if isinstance(node, Str):
|
||||
text = node.s
|
||||
elif isinstance(node, Constant) and isinstance(node.value, str):
|
||||
if isinstance(node, Constant) and isinstance(node.value, str):
|
||||
text = node.value
|
||||
else:
|
||||
return None
|
||||
@@ -304,28 +304,17 @@ def get_docstring(node, clean=True):
|
||||
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.
|
||||
|
||||
This mimics how the Python parser splits source code.
|
||||
"""
|
||||
idx = 0
|
||||
lines = []
|
||||
next_line = ''
|
||||
while idx < len(source):
|
||||
c = source[idx]
|
||||
next_line += c
|
||||
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)
|
||||
for lineno, match in enumerate(_line_pattern.finditer(source), 1):
|
||||
if maxlines is not None and lineno > maxlines:
|
||||
break
|
||||
lines.append(match[0])
|
||||
return lines
|
||||
|
||||
|
||||
@@ -359,7 +348,7 @@ def get_source_segment(source, node, *, padded=False):
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
lines = _splitlines_no_ff(source)
|
||||
lines = _splitlines_no_ff(source, maxlines=end_lineno+1)
|
||||
if end_lineno == lineno:
|
||||
return lines[lineno].encode()[col_offset:end_col_offset].decode()
|
||||
|
||||
@@ -508,20 +497,52 @@ class NodeTransformer(NodeVisitor):
|
||||
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 not hasattr(Constant, 'n'):
|
||||
# The following code is for backward compatibility.
|
||||
# It will be removed in future.
|
||||
|
||||
def _getter(self):
|
||||
def _n_getter(self):
|
||||
"""Deprecated. Use value instead."""
|
||||
import warnings
|
||||
warnings._deprecated(
|
||||
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
|
||||
)
|
||||
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
|
||||
|
||||
Constant.n = property(_getter, _setter)
|
||||
Constant.s = property(_getter, _setter)
|
||||
def _s_getter(self):
|
||||
"""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):
|
||||
|
||||
@@ -529,6 +550,13 @@ class _ABC(type):
|
||||
cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead"""
|
||||
|
||||
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):
|
||||
return False
|
||||
if cls in _const_types:
|
||||
@@ -552,6 +580,10 @@ def _new(cls, *args, **kwargs):
|
||||
if pos < len(args):
|
||||
raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}")
|
||||
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.__new__(cls, *args, **kwargs)
|
||||
|
||||
@@ -574,10 +606,19 @@ class Ellipsis(Constant, metaclass=_ABC):
|
||||
_fields = ()
|
||||
|
||||
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.__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 = {
|
||||
Num: (int, float, complex),
|
||||
Str: (str,),
|
||||
@@ -644,10 +685,12 @@ class Param(expr_context):
|
||||
# We unparse those infinities to INFSTR.
|
||||
_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."""
|
||||
|
||||
TUPLE = auto()
|
||||
NAMED_EXPR = auto() # <target> := <expr1>
|
||||
TUPLE = auto() # <expr1>, <expr2>
|
||||
YIELD = auto() # 'yield', 'yield from'
|
||||
TEST = auto() # 'if'-'else', 'lambda'
|
||||
OR = auto() # 'or'
|
||||
@@ -685,11 +728,11 @@ class _Unparser(NodeVisitor):
|
||||
|
||||
def __init__(self, *, _avoid_backslashes=False):
|
||||
self._source = []
|
||||
self._buffer = []
|
||||
self._precedences = {}
|
||||
self._type_ignores = {}
|
||||
self._indent = 0
|
||||
self._avoid_backslashes = _avoid_backslashes
|
||||
self._in_try_star = False
|
||||
|
||||
def interleave(self, inter, f, seq):
|
||||
"""Call f on each item in seq, calling inter() in between."""
|
||||
@@ -724,18 +767,19 @@ class _Unparser(NodeVisitor):
|
||||
self.maybe_newline()
|
||||
self.write(" " * self._indent + text)
|
||||
|
||||
def write(self, text):
|
||||
"""Append a piece of text"""
|
||||
self._source.append(text)
|
||||
def write(self, *text):
|
||||
"""Add new source parts"""
|
||||
self._source.extend(text)
|
||||
|
||||
def buffer_writer(self, text):
|
||||
self._buffer.append(text)
|
||||
@contextmanager
|
||||
def buffered(self, buffer = None):
|
||||
if buffer is None:
|
||||
buffer = []
|
||||
|
||||
@property
|
||||
def buffer(self):
|
||||
value = "".join(self._buffer)
|
||||
self._buffer.clear()
|
||||
return value
|
||||
original_source = self._source
|
||||
self._source = buffer
|
||||
yield buffer
|
||||
self._source = original_source
|
||||
|
||||
@contextmanager
|
||||
def block(self, *, extra = None):
|
||||
@@ -845,7 +889,7 @@ class _Unparser(NodeVisitor):
|
||||
self.traverse(node.value)
|
||||
|
||||
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.traverse(node.target)
|
||||
self.write(" := ")
|
||||
@@ -866,6 +910,7 @@ class _Unparser(NodeVisitor):
|
||||
def visit_Assign(self, node):
|
||||
self.fill()
|
||||
for target in node.targets:
|
||||
self.set_precedence(_Precedence.TUPLE, target)
|
||||
self.traverse(target)
|
||||
self.write(" = ")
|
||||
self.traverse(node.value)
|
||||
@@ -958,7 +1003,7 @@ class _Unparser(NodeVisitor):
|
||||
self.write(" from ")
|
||||
self.traverse(node.cause)
|
||||
|
||||
def visit_Try(self, node):
|
||||
def do_visit_try(self, node):
|
||||
self.fill("try")
|
||||
with self.block():
|
||||
self.traverse(node.body)
|
||||
@@ -973,8 +1018,24 @@ class _Unparser(NodeVisitor):
|
||||
with self.block():
|
||||
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):
|
||||
self.fill("except")
|
||||
self.fill("except*" if self._in_try_star else "except")
|
||||
if node.type:
|
||||
self.write(" ")
|
||||
self.traverse(node.type)
|
||||
@@ -990,6 +1051,8 @@ class _Unparser(NodeVisitor):
|
||||
self.fill("@")
|
||||
self.traverse(deco)
|
||||
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):
|
||||
comma = False
|
||||
for e in node.bases:
|
||||
@@ -1021,6 +1084,8 @@ class _Unparser(NodeVisitor):
|
||||
self.traverse(deco)
|
||||
def_str = fill_suffix + " " + node.name
|
||||
self.fill(def_str)
|
||||
if hasattr(node, "type_params"):
|
||||
self._type_params_helper(node.type_params)
|
||||
with self.delimit("(", ")"):
|
||||
self.traverse(node.args)
|
||||
if node.returns:
|
||||
@@ -1029,6 +1094,30 @@ class _Unparser(NodeVisitor):
|
||||
with self.block(extra=self.get_type_comment(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):
|
||||
self._for_helper("for ", node)
|
||||
|
||||
@@ -1037,6 +1126,7 @@ class _Unparser(NodeVisitor):
|
||||
|
||||
def _for_helper(self, fill, node):
|
||||
self.fill(fill)
|
||||
self.set_precedence(_Precedence.TUPLE, node.target)
|
||||
self.traverse(node.target)
|
||||
self.write(" in ")
|
||||
self.traverse(node.iter)
|
||||
@@ -1133,71 +1223,81 @@ class _Unparser(NodeVisitor):
|
||||
|
||||
def visit_JoinedStr(self, node):
|
||||
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
|
||||
# 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 = []
|
||||
fstring_parts = []
|
||||
for value in node.values:
|
||||
meth = getattr(self, "_fstring_" + type(value).__name__)
|
||||
meth(value, self.buffer_writer)
|
||||
buffer.append((self.buffer, isinstance(value, Constant)))
|
||||
new_buffer = []
|
||||
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
|
||||
with self.buffered() as buffer:
|
||||
self._write_fstring_inner(value)
|
||||
fstring_parts.append(
|
||||
("".join(buffer), isinstance(value, 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]
|
||||
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):
|
||||
self.write("f")
|
||||
self._fstring_FormattedValue(node, self.buffer_writer)
|
||||
self._write_str_avoiding_backslashes(self.buffer)
|
||||
def unparse_inner(inner):
|
||||
unparser = type(self)()
|
||||
unparser.set_precedence(_Precedence.TEST.next(), inner)
|
||||
return unparser.visit(inner)
|
||||
|
||||
def _fstring_JoinedStr(self, node, write):
|
||||
for value in node.values:
|
||||
meth = getattr(self, "_fstring_" + type(value).__name__)
|
||||
meth(value, write)
|
||||
|
||||
def _fstring_Constant(self, node, write):
|
||||
if not isinstance(node.value, str):
|
||||
raise ValueError("Constants inside JoinedStr should be a string.")
|
||||
value = node.value.replace("{", "{{").replace("}", "}}")
|
||||
write(value)
|
||||
|
||||
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("}")
|
||||
with self.delimit("{", "}"):
|
||||
expr = unparse_inner(node.value)
|
||||
if expr.startswith("{"):
|
||||
# Separate pair of opening brackets as "{ {"
|
||||
self.write(" ")
|
||||
self.write(expr)
|
||||
if node.conversion != -1:
|
||||
self.write(f"!{chr(node.conversion)}")
|
||||
if node.format_spec:
|
||||
self.write(":")
|
||||
self._write_fstring_inner(node.format_spec)
|
||||
|
||||
def visit_Name(self, node):
|
||||
self.write(node.id)
|
||||
@@ -1320,7 +1420,11 @@ class _Unparser(NodeVisitor):
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"}
|
||||
@@ -1336,7 +1440,7 @@ class _Unparser(NodeVisitor):
|
||||
operator_precedence = self.unop_precedence[operator]
|
||||
with self.require_parens(operator_precedence, node):
|
||||
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)
|
||||
if operator_precedence is not _Precedence.FACTOR:
|
||||
self.write(" ")
|
||||
@@ -1461,20 +1565,17 @@ class _Unparser(NodeVisitor):
|
||||
self.traverse(e)
|
||||
|
||||
def visit_Subscript(self, node):
|
||||
def is_simple_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).
|
||||
def is_non_empty_tuple(slice_value):
|
||||
return (
|
||||
isinstance(slice_value, Tuple)
|
||||
and slice_value.elts
|
||||
and not any(isinstance(elt, Starred) for elt in slice_value.elts)
|
||||
)
|
||||
|
||||
self.set_precedence(_Precedence.ATOM, node.value)
|
||||
self.traverse(node.value)
|
||||
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)
|
||||
else:
|
||||
self.traverse(node.slice)
|
||||
@@ -1571,8 +1672,11 @@ class _Unparser(NodeVisitor):
|
||||
|
||||
def visit_Lambda(self, node):
|
||||
with self.require_parens(_Precedence.TEST, node):
|
||||
self.write("lambda ")
|
||||
self.traverse(node.args)
|
||||
self.write("lambda")
|
||||
with self.buffered() as buffer:
|
||||
self.traverse(node.args)
|
||||
if buffer:
|
||||
self.write(" ", *buffer)
|
||||
self.write(": ")
|
||||
self.set_precedence(_Precedence.TEST, node.body)
|
||||
self.traverse(node.body)
|
||||
@@ -1681,6 +1785,22 @@ def unparse(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():
|
||||
import argparse
|
||||
|
||||
|
||||
19
Lib/asyncio/__init__.py
vendored
19
Lib/asyncio/__init__.py
vendored
@@ -1,22 +1,14 @@
|
||||
"""The asyncio package, tracking PEP 3156."""
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
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.
|
||||
from .base_events import *
|
||||
from .coroutines import *
|
||||
from .events import *
|
||||
from .exceptions import *
|
||||
from .futures import *
|
||||
from .locks import *
|
||||
from .protocols import *
|
||||
@@ -25,11 +17,15 @@ from .queues import *
|
||||
from .streams import *
|
||||
from .subprocess import *
|
||||
from .tasks import *
|
||||
from .taskgroups import *
|
||||
from .timeouts import *
|
||||
from .threads import *
|
||||
from .transports import *
|
||||
|
||||
__all__ = (base_events.__all__ +
|
||||
coroutines.__all__ +
|
||||
events.__all__ +
|
||||
exceptions.__all__ +
|
||||
futures.__all__ +
|
||||
locks.__all__ +
|
||||
protocols.__all__ +
|
||||
@@ -38,6 +34,9 @@ __all__ = (base_events.__all__ +
|
||||
streams.__all__ +
|
||||
subprocess.__all__ +
|
||||
tasks.__all__ +
|
||||
taskgroups.__all__ +
|
||||
threads.__all__ +
|
||||
timeouts.__all__ +
|
||||
transports.__all__)
|
||||
|
||||
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
|
||||
|
||||
from . import events
|
||||
|
||||
Error = concurrent.futures._base.Error
|
||||
CancelledError = concurrent.futures.CancelledError
|
||||
TimeoutError = concurrent.futures.TimeoutError
|
||||
|
||||
|
||||
class InvalidStateError(Error):
|
||||
"""The operation is not allowed in this state."""
|
||||
|
||||
from . import format_helpers
|
||||
|
||||
# States for Future.
|
||||
_PENDING = 'PENDING'
|
||||
@@ -38,17 +28,17 @@ def _format_callbacks(cb):
|
||||
cb = ''
|
||||
|
||||
def format_cb(callback):
|
||||
return events._format_callback_source(callback, ())
|
||||
return format_helpers._format_callback_source(callback, ())
|
||||
|
||||
if size == 1:
|
||||
cb = format_cb(cb[0])
|
||||
cb = format_cb(cb[0][0])
|
||||
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:
|
||||
cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
|
||||
cb = '{}, <{} more>, {}'.format(format_cb(cb[0][0]),
|
||||
size - 2,
|
||||
format_cb(cb[-1]))
|
||||
return 'cb=[%s]' % cb
|
||||
format_cb(cb[-1][0]))
|
||||
return f'cb=[{cb}]'
|
||||
|
||||
|
||||
def _future_repr_info(future):
|
||||
@@ -57,15 +47,21 @@ def _future_repr_info(future):
|
||||
info = [future._state.lower()]
|
||||
if future._state == _FINISHED:
|
||||
if future._exception is not None:
|
||||
info.append('exception={!r}'.format(future._exception))
|
||||
info.append(f'exception={future._exception!r}')
|
||||
else:
|
||||
# use reprlib to limit the length of the output, especially
|
||||
# for very long strings
|
||||
result = reprlib.repr(future._result)
|
||||
info.append('result={}'.format(result))
|
||||
info.append(f'result={result}')
|
||||
if future._callbacks:
|
||||
info.append(_format_callbacks(future._callbacks))
|
||||
if future._source_traceback:
|
||||
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
|
||||
|
||||
|
||||
@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 warnings
|
||||
|
||||
from . import compat
|
||||
from . import protocols
|
||||
from . import transports
|
||||
from .coroutines import coroutine
|
||||
from .log import logger
|
||||
|
||||
|
||||
@@ -59,9 +57,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
if self._closed:
|
||||
info.append('closed')
|
||||
if self._pid is not None:
|
||||
info.append('pid=%s' % self._pid)
|
||||
info.append(f'pid={self._pid}')
|
||||
if self._returncode is not None:
|
||||
info.append('returncode=%s' % self._returncode)
|
||||
info.append(f'returncode={self._returncode}')
|
||||
elif self._pid is not None:
|
||||
info.append('running')
|
||||
else:
|
||||
@@ -69,19 +67,19 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
|
||||
stdin = self._pipes.get(0)
|
||||
if stdin is not None:
|
||||
info.append('stdin=%s' % stdin.pipe)
|
||||
info.append(f'stdin={stdin.pipe}')
|
||||
|
||||
stdout = self._pipes.get(1)
|
||||
stderr = self._pipes.get(2)
|
||||
if stdout is not None and stderr is stdout:
|
||||
info.append('stdout=stderr=%s' % stdout.pipe)
|
||||
info.append(f'stdout=stderr={stdout.pipe}')
|
||||
else:
|
||||
if stdout is not None:
|
||||
info.append('stdout=%s' % stdout.pipe)
|
||||
info.append(f'stdout={stdout.pipe}')
|
||||
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):
|
||||
raise NotImplementedError
|
||||
@@ -105,12 +103,13 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
continue
|
||||
proto.pipe.close()
|
||||
|
||||
if (self._proc is not None
|
||||
# the child process finished?
|
||||
and self._returncode is None
|
||||
# the child process finished but the transport was not notified yet?
|
||||
and self._proc.poll() is None
|
||||
):
|
||||
if (self._proc is not None and
|
||||
# has the child process finished?
|
||||
self._returncode is None and
|
||||
# the child process has finished, but the
|
||||
# transport hasn't been notified yet?
|
||||
self._proc.poll() is None):
|
||||
|
||||
if self._loop.get_debug():
|
||||
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
|
||||
|
||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
||||
# to the PEP 442.
|
||||
if compat.PY34:
|
||||
def __del__(self):
|
||||
if not self._closed:
|
||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||
source=self)
|
||||
self.close()
|
||||
def __del__(self, _warn=warnings.warn):
|
||||
if not self._closed:
|
||||
_warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
|
||||
self.close()
|
||||
|
||||
def get_pid(self):
|
||||
return self._pid
|
||||
@@ -159,26 +153,25 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
self._check_proc()
|
||||
self._proc.kill()
|
||||
|
||||
@coroutine
|
||||
def _connect_pipes(self, waiter):
|
||||
async def _connect_pipes(self, waiter):
|
||||
try:
|
||||
proc = self._proc
|
||||
loop = self._loop
|
||||
|
||||
if proc.stdin is not None:
|
||||
_, pipe = yield from loop.connect_write_pipe(
|
||||
_, pipe = await loop.connect_write_pipe(
|
||||
lambda: WriteSubprocessPipeProto(self, 0),
|
||||
proc.stdin)
|
||||
self._pipes[0] = pipe
|
||||
|
||||
if proc.stdout is not None:
|
||||
_, pipe = yield from loop.connect_read_pipe(
|
||||
_, pipe = await loop.connect_read_pipe(
|
||||
lambda: ReadSubprocessPipeProto(self, 1),
|
||||
proc.stdout)
|
||||
self._pipes[1] = pipe
|
||||
|
||||
if proc.stderr is not None:
|
||||
_, pipe = yield from loop.connect_read_pipe(
|
||||
_, pipe = await loop.connect_read_pipe(
|
||||
lambda: ReadSubprocessPipeProto(self, 2),
|
||||
proc.stderr)
|
||||
self._pipes[2] = pipe
|
||||
@@ -189,7 +182,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
for callback, data in self._pending_calls:
|
||||
loop.call_soon(callback, *data)
|
||||
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():
|
||||
waiter.set_exception(exc)
|
||||
else:
|
||||
@@ -213,24 +208,17 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
assert returncode is not None, returncode
|
||||
assert self._returncode is None, self._returncode
|
||||
if self._loop.get_debug():
|
||||
logger.info('%r exited with return code %r',
|
||||
self, returncode)
|
||||
logger.info('%r exited with return code %r', self, returncode)
|
||||
self._returncode = returncode
|
||||
if self._proc.returncode is None:
|
||||
# asyncio uses a child watcher: copy the status into the Popen
|
||||
# object. On Python 3.6, it is required to avoid a ResourceWarning.
|
||||
self._proc.returncode = returncode
|
||||
self._call(self._protocol.process_exited)
|
||||
|
||||
self._try_finish()
|
||||
|
||||
# wake up futures waiting for wait()
|
||||
for waiter in self._exit_waiters:
|
||||
if not waiter.cancelled():
|
||||
waiter.set_result(returncode)
|
||||
self._exit_waiters = None
|
||||
|
||||
@coroutine
|
||||
def _wait(self):
|
||||
async def _wait(self):
|
||||
"""Wait until the process exit and return the process return code.
|
||||
|
||||
This method is a coroutine."""
|
||||
@@ -239,7 +227,7 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
|
||||
waiter = self._loop.create_future()
|
||||
self._exit_waiters.append(waiter)
|
||||
return (yield from waiter)
|
||||
return await waiter
|
||||
|
||||
def _try_finish(self):
|
||||
assert not self._finished
|
||||
@@ -254,6 +242,11 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
|
||||
try:
|
||||
self._protocol.connection_lost(exc)
|
||||
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._proc = None
|
||||
self._protocol = None
|
||||
@@ -271,8 +264,7 @@ class WriteSubprocessPipeProto(protocols.BaseProtocol):
|
||||
self.pipe = transport
|
||||
|
||||
def __repr__(self):
|
||||
return ('<%s fd=%s pipe=%r>'
|
||||
% (self.__class__.__name__, self.fd, self.pipe))
|
||||
return f'<{self.__class__.__name__} fd={self.fd} pipe={self.pipe!r}>'
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self.disconnected = True
|
||||
|
||||
42
Lib/asyncio/base_tasks.py
vendored
42
Lib/asyncio/base_tasks.py
vendored
@@ -1,4 +1,5 @@
|
||||
import linecache
|
||||
import reprlib
|
||||
import traceback
|
||||
|
||||
from . import base_futures
|
||||
@@ -8,25 +9,42 @@ from . import coroutines
|
||||
def _task_repr_info(task):
|
||||
info = base_futures._future_repr_info(task)
|
||||
|
||||
if task._must_cancel:
|
||||
if task.cancelling() and not task.done():
|
||||
# replace status
|
||||
info[0] = 'cancelling'
|
||||
|
||||
coro = coroutines._format_coroutine(task._coro)
|
||||
info.insert(1, 'coro=<%s>' % coro)
|
||||
info.insert(1, 'name=%r' % task.get_name())
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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):
|
||||
frames = []
|
||||
try:
|
||||
# 'async def' coroutines
|
||||
if hasattr(task._coro, 'cr_frame'):
|
||||
# case 1: 'async def' coroutines
|
||||
f = task._coro.cr_frame
|
||||
except AttributeError:
|
||||
elif hasattr(task._coro, 'gi_frame'):
|
||||
# case 2: legacy coroutines
|
||||
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:
|
||||
while f is not None:
|
||||
if limit is not None:
|
||||
@@ -61,15 +79,15 @@ def _task_print_stack(task, limit, file):
|
||||
linecache.checkcache(filename)
|
||||
line = linecache.getline(filename, lineno, f.f_globals)
|
||||
extracted_list.append((filename, lineno, name, line))
|
||||
|
||||
exc = task._exception
|
||||
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:
|
||||
print('Traceback for %r (most recent call last):' % task,
|
||||
file=file)
|
||||
print(f'Traceback for {task!r} (most recent call last):', file=file)
|
||||
else:
|
||||
print('Stack for %r (most recent call last):' % task,
|
||||
file=file)
|
||||
print(f'Stack for {task!r} (most recent call last):', file=file)
|
||||
|
||||
traceback.print_list(extracted_list, file=file)
|
||||
if exc is not None:
|
||||
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.
|
||||
LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
|
||||
|
||||
# Seconds to wait before retrying accept().
|
||||
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',
|
||||
'iscoroutinefunction', 'iscoroutine']
|
||||
__all__ = 'iscoroutinefunction', 'iscoroutine'
|
||||
|
||||
import functools
|
||||
import collections.abc
|
||||
import inspect
|
||||
import opcode
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
|
||||
from . import compat
|
||||
from . import events
|
||||
from . import base_futures
|
||||
from .log import logger
|
||||
|
||||
|
||||
# Opcode of "yield from" instruction
|
||||
_YIELD_FROM = opcode.opmap['YIELD_FROM']
|
||||
|
||||
# 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
|
||||
def _is_debug_mode():
|
||||
# See: https://docs.python.org/3/library/asyncio-dev.html#asyncio-debug-mode.
|
||||
return sys.flags.dev_mode or (not sys.flags.ignore_environment and
|
||||
bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
||||
|
||||
|
||||
# A marker for iscoroutinefunction.
|
||||
@@ -252,93 +19,91 @@ _is_coroutine = object()
|
||||
|
||||
def iscoroutinefunction(func):
|
||||
"""Return True if func is a decorated coroutine function."""
|
||||
return (getattr(func, '_is_coroutine', None) is _is_coroutine or
|
||||
_inspect_iscoroutinefunction(func))
|
||||
return (inspect.iscoroutinefunction(func) or
|
||||
getattr(func, '_is_coroutine', None) is _is_coroutine)
|
||||
|
||||
|
||||
_COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
|
||||
if _CoroutineABC is not None:
|
||||
_COROUTINE_TYPES += (_CoroutineABC,)
|
||||
if _types_CoroutineType is not None:
|
||||
# Prioritize native coroutine check to speed-up
|
||||
# asyncio.iscoroutine.
|
||||
_COROUTINE_TYPES = (_types_CoroutineType,) + _COROUTINE_TYPES
|
||||
# Prioritize native coroutine check to speed-up
|
||||
# asyncio.iscoroutine.
|
||||
_COROUTINE_TYPES = (types.CoroutineType, collections.abc.Coroutine)
|
||||
_iscoroutine_typecache = set()
|
||||
|
||||
|
||||
def iscoroutine(obj):
|
||||
"""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):
|
||||
assert iscoroutine(coro)
|
||||
|
||||
if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'):
|
||||
# Most likely a built-in type or a Cython coroutine.
|
||||
def get_name(coro):
|
||||
# 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__.
|
||||
coro_name = getattr(
|
||||
coro, '__qualname__',
|
||||
getattr(coro, '__name__', type(coro).__name__))
|
||||
coro_name = '{}()'.format(coro_name)
|
||||
|
||||
running = False
|
||||
def is_running(coro):
|
||||
try:
|
||||
running = coro.cr_running
|
||||
return coro.cr_running
|
||||
except AttributeError:
|
||||
try:
|
||||
running = coro.gi_running
|
||||
return coro.gi_running
|
||||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
if running:
|
||||
return '{} running'.format(coro_name)
|
||||
coro_code = None
|
||||
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:
|
||||
return coro_name
|
||||
|
||||
coro_name = None
|
||||
if isinstance(coro, CoroWrapper):
|
||||
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 = None
|
||||
if hasattr(coro, 'gi_frame') and coro.gi_frame:
|
||||
coro_frame = coro.gi_frame
|
||||
except AttributeError:
|
||||
elif hasattr(coro, 'cr_frame') and 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
|
||||
if (isinstance(coro, CoroWrapper) and
|
||||
not inspect.isgeneratorfunction(coro.func) and
|
||||
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:
|
||||
|
||||
if coro_frame is not None:
|
||||
lineno = coro_frame.f_lineno
|
||||
coro_repr = ('%s running at %s:%s'
|
||||
% (coro_name, filename, lineno))
|
||||
coro_repr = f'{coro_name} running at {filename}:{lineno}'
|
||||
|
||||
else:
|
||||
lineno = coro_code.co_firstlineno
|
||||
coro_repr = ('%s done, defined at %s:%s'
|
||||
% (coro_name, filename, lineno))
|
||||
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
|
||||
|
||||
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."""
|
||||
|
||||
__all__ = ['AbstractEventLoopPolicy',
|
||||
'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',
|
||||
]
|
||||
# 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 functools
|
||||
import inspect
|
||||
import reprlib
|
||||
__all__ = (
|
||||
'AbstractEventLoopPolicy',
|
||||
'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 subprocess
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from asyncio import compat
|
||||
|
||||
|
||||
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
|
||||
from . import format_helpers
|
||||
|
||||
|
||||
class Handle:
|
||||
"""Object returned by callback registration methods."""
|
||||
|
||||
__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._callback = callback
|
||||
self._args = args
|
||||
self._cancelled = False
|
||||
self._repr = None
|
||||
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:
|
||||
self._source_traceback = None
|
||||
|
||||
@@ -99,17 +53,21 @@ class Handle:
|
||||
if self._cancelled:
|
||||
info.append('cancelled')
|
||||
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:
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
if self._repr is not None:
|
||||
return self._repr
|
||||
info = self._repr_info()
|
||||
return '<%s>' % ' '.join(info)
|
||||
return '<{}>'.format(' '.join(info))
|
||||
|
||||
def get_context(self):
|
||||
return self._context
|
||||
|
||||
def cancel(self):
|
||||
if not self._cancelled:
|
||||
@@ -122,12 +80,18 @@ class Handle:
|
||||
self._callback = None
|
||||
self._args = None
|
||||
|
||||
def cancelled(self):
|
||||
return self._cancelled
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self._callback(*self._args)
|
||||
except Exception as exc:
|
||||
cb = _format_callback_source(self._callback, self._args)
|
||||
msg = 'Exception in callback {}'.format(cb)
|
||||
self._context.run(self._callback, *self._args)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
cb = format_helpers._format_callback_source(
|
||||
self._callback, self._args)
|
||||
msg = f'Exception in callback {cb}'
|
||||
context = {
|
||||
'message': msg,
|
||||
'exception': exc,
|
||||
@@ -144,9 +108,8 @@ class TimerHandle(Handle):
|
||||
|
||||
__slots__ = ['_scheduled', '_when']
|
||||
|
||||
def __init__(self, when, callback, args, loop):
|
||||
assert when is not None
|
||||
super().__init__(callback, args, loop)
|
||||
def __init__(self, when, callback, args, loop, context=None):
|
||||
super().__init__(callback, args, loop, context)
|
||||
if self._source_traceback:
|
||||
del self._source_traceback[-1]
|
||||
self._when = when
|
||||
@@ -155,27 +118,31 @@ class TimerHandle(Handle):
|
||||
def _repr_info(self):
|
||||
info = super()._repr_info()
|
||||
pos = 2 if self._cancelled else 1
|
||||
info.insert(pos, 'when=%s' % self._when)
|
||||
info.insert(pos, f'when={self._when}')
|
||||
return info
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._when)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._when < other._when
|
||||
if isinstance(other, TimerHandle):
|
||||
return self._when < other._when
|
||||
return NotImplemented
|
||||
|
||||
def __le__(self, other):
|
||||
if self._when < other._when:
|
||||
return True
|
||||
return self.__eq__(other)
|
||||
if isinstance(other, TimerHandle):
|
||||
return self._when < other._when or self.__eq__(other)
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._when > other._when
|
||||
if isinstance(other, TimerHandle):
|
||||
return self._when > other._when
|
||||
return NotImplemented
|
||||
|
||||
def __ge__(self, other):
|
||||
if self._when > other._when:
|
||||
return True
|
||||
return self.__eq__(other)
|
||||
if isinstance(other, TimerHandle):
|
||||
return self._when > other._when or self.__eq__(other)
|
||||
return NotImplemented
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, TimerHandle):
|
||||
@@ -185,26 +152,60 @@ class TimerHandle(Handle):
|
||||
self._cancelled == other._cancelled)
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
equal = self.__eq__(other)
|
||||
return NotImplemented if equal is NotImplemented else not equal
|
||||
|
||||
def cancel(self):
|
||||
if not self._cancelled:
|
||||
self._loop._timer_handle_cancelled(self)
|
||||
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:
|
||||
"""Abstract server returned by create_server()."""
|
||||
|
||||
def close(self):
|
||||
"""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."""
|
||||
return NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *exc):
|
||||
self.close()
|
||||
await self.wait_closed()
|
||||
|
||||
|
||||
class AbstractEventLoop:
|
||||
@@ -250,23 +251,27 @@ class AbstractEventLoop:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def shutdown_asyncgens(self):
|
||||
async def shutdown_asyncgens(self):
|
||||
"""Shutdown all active asynchronous generators."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def shutdown_default_executor(self):
|
||||
"""Schedule the shutdown of the default executor."""
|
||||
raise NotImplementedError
|
||||
|
||||
# Methods scheduling callbacks. All these return Handles.
|
||||
|
||||
def _timer_handle_cancelled(self, handle):
|
||||
"""Notification that a TimerHandle has been cancelled."""
|
||||
raise NotImplementedError
|
||||
|
||||
def call_soon(self, callback, *args):
|
||||
return self.call_later(0, callback, *args)
|
||||
def call_soon(self, callback, *args, context=None):
|
||||
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
|
||||
|
||||
def call_at(self, when, callback, *args):
|
||||
def call_at(self, when, callback, *args, context=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def time(self):
|
||||
@@ -277,12 +282,12 @@ class AbstractEventLoop:
|
||||
|
||||
# Method scheduling a coroutine object: create a task.
|
||||
|
||||
def create_task(self, coro):
|
||||
def create_task(self, coro, *, name=None, context=None):
|
||||
raise NotImplementedError
|
||||
|
||||
# Methods for interacting with threads.
|
||||
|
||||
def call_soon_threadsafe(self, callback, *args):
|
||||
def call_soon_threadsafe(self, callback, *args, context=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def run_in_executor(self, executor, func, *args):
|
||||
@@ -293,21 +298,31 @@ class AbstractEventLoop:
|
||||
|
||||
# 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
|
||||
|
||||
def getnameinfo(self, sockaddr, flags=0):
|
||||
async def getnameinfo(self, sockaddr, flags=0):
|
||||
raise NotImplementedError
|
||||
|
||||
def create_connection(self, protocol_factory, host=None, port=None, *,
|
||||
ssl=None, family=0, proto=0, flags=0, sock=None,
|
||||
local_addr=None, server_hostname=None):
|
||||
async def create_connection(
|
||||
self, protocol_factory, host=None, port=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
|
||||
|
||||
def create_server(self, protocol_factory, host=None, port=None, *,
|
||||
family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE,
|
||||
sock=None, backlog=100, ssl=None, reuse_address=None,
|
||||
reuse_port=None):
|
||||
async def create_server(
|
||||
self, protocol_factory, host=None, port=None,
|
||||
*, family=socket.AF_UNSPEC,
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
sequence (e.g. list) of hosts to bind to.
|
||||
one for IPv4 and another one for IPv6). The host parameter can also be
|
||||
a sequence (e.g. list) of hosts to bind to.
|
||||
|
||||
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
|
||||
@@ -342,22 +357,62 @@ class AbstractEventLoop:
|
||||
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
|
||||
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
|
||||
|
||||
def create_unix_connection(self, protocol_factory, path, *,
|
||||
ssl=None, sock=None,
|
||||
server_hostname=None):
|
||||
async def sendfile(self, transport, file, offset=0, count=None,
|
||||
*, fallback=True):
|
||||
"""Send a file through a transport.
|
||||
|
||||
Return an amount of sent bytes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def create_unix_server(self, protocol_factory, path, *,
|
||||
sock=None, backlog=100, ssl=None):
|
||||
async def start_tls(self, transport, protocol, sslcontext, *,
|
||||
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.
|
||||
|
||||
The return value is a Server object, which can be used to stop
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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):
|
||||
async def connect_accepted_socket(
|
||||
self, protocol_factory, sock,
|
||||
*, ssl=None,
|
||||
ssl_handshake_timeout=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.
|
||||
|
||||
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.
|
||||
|
||||
socket family AF_INET or socket.AF_INET6 depending on host (or
|
||||
family if specified), socket type SOCK_DGRAM.
|
||||
socket family AF_INET, socket.AF_INET6 or socket.AF_UNIX depending on
|
||||
host (or family if specified), socket type SOCK_DGRAM.
|
||||
|
||||
reuse_address tells the kernel to reuse a local socket in
|
||||
TIME_WAIT state, without waiting for its natural timeout to
|
||||
@@ -408,7 +489,7 @@ class AbstractEventLoop:
|
||||
|
||||
# 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.
|
||||
|
||||
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
|
||||
# is: we need to own pipe and close it at transport finishing
|
||||
# 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
|
||||
|
||||
def connect_write_pipe(self, protocol_factory, pipe):
|
||||
async def connect_write_pipe(self, protocol_factory, pipe):
|
||||
"""Register write pipe in event loop.
|
||||
|
||||
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
|
||||
# is: we need to own pipe and close it at transport finishing
|
||||
# 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
|
||||
|
||||
def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
**kwargs):
|
||||
async def subprocess_shell(self, protocol_factory, cmd, *,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
**kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
**kwargs):
|
||||
async def subprocess_exec(self, protocol_factory, *args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
**kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
# Ready-based callback registration methods.
|
||||
@@ -463,16 +548,32 @@ class AbstractEventLoop:
|
||||
|
||||
# Completion based I/O methods returning Futures.
|
||||
|
||||
def sock_recv(self, sock, nbytes):
|
||||
async def sock_recv(self, sock, nbytes):
|
||||
raise NotImplementedError
|
||||
|
||||
def sock_sendall(self, sock, data):
|
||||
async def sock_recv_into(self, sock, buf):
|
||||
raise NotImplementedError
|
||||
|
||||
def sock_connect(self, sock, address):
|
||||
async def sock_recvfrom(self, sock, bufsize):
|
||||
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
|
||||
|
||||
# Signal handling.
|
||||
@@ -520,7 +621,7 @@ class AbstractEventLoopPolicy:
|
||||
def get_event_loop(self):
|
||||
"""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
|
||||
current context and the current policy does not specify to create one.
|
||||
|
||||
@@ -571,23 +672,43 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
|
||||
self._local = self._Local()
|
||||
|
||||
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
|
||||
not self._local._set_called and
|
||||
isinstance(threading.current_thread(), threading._MainThread)):
|
||||
not self._local._set_called and
|
||||
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())
|
||||
|
||||
if self._local._loop is None:
|
||||
raise RuntimeError('There is no current event loop in thread %r.'
|
||||
% threading.current_thread().name)
|
||||
|
||||
return self._local._loop
|
||||
|
||||
def set_event_loop(self, loop):
|
||||
"""Set the event loop."""
|
||||
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
|
||||
|
||||
def new_event_loop(self):
|
||||
@@ -611,7 +732,9 @@ _lock = threading.Lock()
|
||||
|
||||
# A TLS for the running event loop, used by _get_running_loop.
|
||||
class _RunningLoop(threading.local):
|
||||
_loop = None
|
||||
loop_pid = (None, None)
|
||||
|
||||
|
||||
_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 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):
|
||||
@@ -642,7 +768,8 @@ def _set_running_loop(loop):
|
||||
This is a low-level function intended to be used by event loops.
|
||||
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():
|
||||
@@ -665,7 +792,8 @@ def set_event_loop_policy(policy):
|
||||
|
||||
If policy is None, the default policy is restored."""
|
||||
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
|
||||
|
||||
|
||||
@@ -678,6 +806,7 @@ def get_event_loop():
|
||||
If there is no running event loop set, the function will return
|
||||
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()
|
||||
if current_loop is not None:
|
||||
return current_loop
|
||||
@@ -703,3 +832,37 @@ def set_child_watcher(watcher):
|
||||
"""Equivalent to calling
|
||||
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."""
|
||||
|
||||
__all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError',
|
||||
'Future', 'wrap_future', 'isfuture']
|
||||
__all__ = (
|
||||
'Future', 'wrap_future', 'isfuture',
|
||||
)
|
||||
|
||||
import concurrent.futures
|
||||
import contextvars
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
from types import GenericAlias
|
||||
|
||||
from . import base_futures
|
||||
from . import compat
|
||||
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
|
||||
|
||||
|
||||
@@ -27,96 +27,18 @@ _FINISHED = base_futures._FINISHED
|
||||
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:
|
||||
"""This class is *almost* compatible with concurrent.futures.Future.
|
||||
|
||||
Differences:
|
||||
|
||||
- This class is not thread-safe.
|
||||
|
||||
- result() and exception() do not take a timeout argument and
|
||||
raise an exception when the future isn't done yet.
|
||||
|
||||
- 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()
|
||||
methods in the concurrent.futures package.
|
||||
@@ -130,6 +52,9 @@ class Future:
|
||||
_exception = None
|
||||
_loop = 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:
|
||||
# - 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
|
||||
# that it is not compatible by setting this to None.
|
||||
# - 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).
|
||||
_asyncio_future_blocking = False
|
||||
|
||||
_log_traceback = False # Used for Python 3.4 and later
|
||||
_tb_logger = None # Used for Python 3.3 only
|
||||
__log_traceback = False
|
||||
|
||||
def __init__(self, *, loop=None):
|
||||
"""Initialize the future.
|
||||
@@ -157,50 +82,83 @@ class Future:
|
||||
self._loop = loop
|
||||
self._callbacks = []
|
||||
if self._loop.get_debug():
|
||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
||||
|
||||
_repr_info = base_futures._future_repr_info
|
||||
self._source_traceback = format_helpers.extract_stack(
|
||||
sys._getframe(1))
|
||||
|
||||
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
|
||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
||||
# to the PEP 442.
|
||||
if compat.PY34:
|
||||
def __del__(self):
|
||||
if not self._log_traceback:
|
||||
# set_exception() was not called, or result() or exception()
|
||||
# has consumed the exception
|
||||
return
|
||||
exc = self._exception
|
||||
context = {
|
||||
'message': ('%s exception was never retrieved'
|
||||
% self.__class__.__name__),
|
||||
'exception': exc,
|
||||
'future': self,
|
||||
}
|
||||
if self._source_traceback:
|
||||
context['source_traceback'] = self._source_traceback
|
||||
self._loop.call_exception_handler(context)
|
||||
def __del__(self):
|
||||
if not self.__log_traceback:
|
||||
# set_exception() was not called, or result() or exception()
|
||||
# has consumed the exception
|
||||
return
|
||||
exc = self._exception
|
||||
context = {
|
||||
'message':
|
||||
f'{self.__class__.__name__} exception was never retrieved',
|
||||
'exception': exc,
|
||||
'future': self,
|
||||
}
|
||||
if self._source_traceback:
|
||||
context['source_traceback'] = self._source_traceback
|
||||
self._loop.call_exception_handler(context)
|
||||
|
||||
def __class_getitem__(cls, type):
|
||||
return cls
|
||||
__class_getitem__ = classmethod(GenericAlias)
|
||||
|
||||
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.
|
||||
|
||||
If the future is already done or cancelled, return False. Otherwise,
|
||||
change the future's state to cancelled, schedule the callbacks and
|
||||
return True.
|
||||
"""
|
||||
self.__log_traceback = False
|
||||
if self._state != _PENDING:
|
||||
return False
|
||||
self._state = _CANCELLED
|
||||
self._schedule_callbacks()
|
||||
self._cancel_message = msg
|
||||
self.__schedule_callbacks()
|
||||
return True
|
||||
|
||||
def _schedule_callbacks(self):
|
||||
def __schedule_callbacks(self):
|
||||
"""Internal: Ask the event loop to call all callbacks.
|
||||
|
||||
The callbacks are scheduled to be called as soon as possible. Also
|
||||
@@ -211,8 +169,8 @@ class Future:
|
||||
return
|
||||
|
||||
self._callbacks[:] = []
|
||||
for callback in callbacks:
|
||||
self._loop.call_soon(callback, self)
|
||||
for callback, ctx in callbacks:
|
||||
self._loop.call_soon(callback, self, context=ctx)
|
||||
|
||||
def cancelled(self):
|
||||
"""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.
|
||||
"""
|
||||
if self._state == _CANCELLED:
|
||||
raise CancelledError
|
||||
exc = self._make_cancelled_error()
|
||||
raise exc
|
||||
if self._state != _FINISHED:
|
||||
raise InvalidStateError('Result is not ready.')
|
||||
self._log_traceback = False
|
||||
if self._tb_logger is not None:
|
||||
self._tb_logger.clear()
|
||||
self._tb_logger = None
|
||||
raise exceptions.InvalidStateError('Result is not ready.')
|
||||
self.__log_traceback = False
|
||||
if self._exception is not None:
|
||||
raise self._exception
|
||||
raise self._exception.with_traceback(self._exception_tb)
|
||||
return self._result
|
||||
|
||||
def exception(self):
|
||||
@@ -256,16 +212,14 @@ class Future:
|
||||
InvalidStateError.
|
||||
"""
|
||||
if self._state == _CANCELLED:
|
||||
raise CancelledError
|
||||
exc = self._make_cancelled_error()
|
||||
raise exc
|
||||
if self._state != _FINISHED:
|
||||
raise InvalidStateError('Exception is not set.')
|
||||
self._log_traceback = False
|
||||
if self._tb_logger is not None:
|
||||
self._tb_logger.clear()
|
||||
self._tb_logger = None
|
||||
raise exceptions.InvalidStateError('Exception is not set.')
|
||||
self.__log_traceback = False
|
||||
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.
|
||||
|
||||
The callback is called with a single argument - the future object. If
|
||||
@@ -273,9 +227,11 @@ class Future:
|
||||
scheduled with call_soon.
|
||||
"""
|
||||
if self._state != _PENDING:
|
||||
self._loop.call_soon(fn, self)
|
||||
self._loop.call_soon(fn, self, context=context)
|
||||
else:
|
||||
self._callbacks.append(fn)
|
||||
if context is None:
|
||||
context = contextvars.copy_context()
|
||||
self._callbacks.append((fn, context))
|
||||
|
||||
# New method not in PEP 3148.
|
||||
|
||||
@@ -284,7 +240,9 @@ class Future:
|
||||
|
||||
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)
|
||||
if removed_count:
|
||||
self._callbacks[:] = filtered_callbacks
|
||||
@@ -299,10 +257,10 @@ class Future:
|
||||
InvalidStateError.
|
||||
"""
|
||||
if self._state != _PENDING:
|
||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
||||
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
|
||||
self._result = result
|
||||
self._state = _FINISHED
|
||||
self._schedule_callbacks()
|
||||
self.__schedule_callbacks()
|
||||
|
||||
def set_exception(self, exception):
|
||||
"""Mark the future done and set an exception.
|
||||
@@ -311,38 +269,45 @@ class Future:
|
||||
InvalidStateError.
|
||||
"""
|
||||
if self._state != _PENDING:
|
||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
||||
raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
|
||||
if isinstance(exception, type):
|
||||
exception = exception()
|
||||
if type(exception) is StopIteration:
|
||||
raise TypeError("StopIteration interacts badly with generators "
|
||||
"and cannot be raised into a Future")
|
||||
self._exception = exception
|
||||
self._exception_tb = exception.__traceback__
|
||||
self._state = _FINISHED
|
||||
self._schedule_callbacks()
|
||||
if compat.PY34:
|
||||
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)
|
||||
self.__schedule_callbacks()
|
||||
self.__log_traceback = True
|
||||
|
||||
def __iter__(self):
|
||||
def __await__(self):
|
||||
if not self.done():
|
||||
self._asyncio_future_blocking = True
|
||||
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.
|
||||
|
||||
if compat.PY35:
|
||||
__await__ = __iter__ # make compatible with 'await' expression
|
||||
__iter__ = __await__ # make compatible with 'yield from'.
|
||||
|
||||
|
||||
# Needed for testing purposes.
|
||||
_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):
|
||||
"""Helper setting the result only if the future was not cancelled."""
|
||||
if fut.cancelled():
|
||||
@@ -350,6 +315,18 @@ def _set_result_unless_cancelled(fut, 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):
|
||||
"""Copy state from a future to a concurrent.futures.Future."""
|
||||
assert source.done()
|
||||
@@ -359,7 +336,7 @@ def _set_concurrent_future_state(concurrent, source):
|
||||
return
|
||||
exception = source.exception()
|
||||
if exception is not None:
|
||||
concurrent.set_exception(exception)
|
||||
concurrent.set_exception(_convert_future_exc(exception))
|
||||
else:
|
||||
result = source.result()
|
||||
concurrent.set_result(result)
|
||||
@@ -379,7 +356,7 @@ def _copy_future_state(source, dest):
|
||||
else:
|
||||
exception = source.exception()
|
||||
if exception is not None:
|
||||
dest.set_exception(exception)
|
||||
dest.set_exception(_convert_future_exc(exception))
|
||||
else:
|
||||
result = source.result()
|
||||
dest.set_result(result)
|
||||
@@ -398,8 +375,8 @@ def _chain_future(source, destination):
|
||||
if not isfuture(destination) and not isinstance(destination,
|
||||
concurrent.futures.Future):
|
||||
raise TypeError('A future is required for destination argument')
|
||||
source_loop = source._loop if isfuture(source) else None
|
||||
dest_loop = destination._loop if isfuture(destination) else None
|
||||
source_loop = _get_loop(source) if isfuture(source) else None
|
||||
dest_loop = _get_loop(destination) if isfuture(destination) else None
|
||||
|
||||
def _set_state(future, other):
|
||||
if isfuture(future):
|
||||
@@ -415,9 +392,14 @@ def _chain_future(source, destination):
|
||||
source_loop.call_soon_threadsafe(source.cancel)
|
||||
|
||||
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:
|
||||
_set_state(destination, source)
|
||||
else:
|
||||
if dest_loop.is_closed():
|
||||
return
|
||||
dest_loop.call_soon_threadsafe(_set_state, destination, source)
|
||||
|
||||
destination.add_done_callback(_call_check_cancel)
|
||||
@@ -429,7 +411,7 @@ def wrap_future(future, *, loop=None):
|
||||
if isfuture(future):
|
||||
return 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:
|
||||
loop = events.get_event_loop()
|
||||
new_future = loop.create_future()
|
||||
|
||||
456
Lib/asyncio/locks.py
vendored
456
Lib/asyncio/locks.py
vendored
@@ -1,92 +1,26 @@
|
||||
"""Synchronization primitives."""
|
||||
|
||||
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
|
||||
__all__ = ('Lock', 'Event', 'Condition', 'Semaphore',
|
||||
'BoundedSemaphore', 'Barrier')
|
||||
|
||||
import collections
|
||||
import enum
|
||||
|
||||
from . import compat
|
||||
from . import events
|
||||
from . import futures
|
||||
from .coroutines import coroutine
|
||||
from . import exceptions
|
||||
from . import mixins
|
||||
|
||||
|
||||
class _ContextManager:
|
||||
"""Context manager.
|
||||
|
||||
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):
|
||||
class _ContextManagerMixin:
|
||||
async def __aenter__(self):
|
||||
await self.acquire()
|
||||
# We have no use for the "as ..." clause in the with
|
||||
# statement for locks.
|
||||
return None
|
||||
|
||||
def __exit__(self, *args):
|
||||
try:
|
||||
self._lock.release()
|
||||
finally:
|
||||
self._lock = None # Crudely prevent reuse.
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
self.release()
|
||||
|
||||
|
||||
class _ContextManagerMixin:
|
||||
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):
|
||||
class Lock(_ContextManagerMixin, mixins._LoopBoundMixin):
|
||||
"""Primitive lock objects.
|
||||
|
||||
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
|
||||
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)'
|
||||
should be used as the context manager expression.
|
||||
Locks also support the asynchronous context management protocol.
|
||||
'async with lock' statement should be used.
|
||||
|
||||
Usage:
|
||||
|
||||
lock = Lock()
|
||||
...
|
||||
yield from lock
|
||||
await lock.acquire()
|
||||
try:
|
||||
...
|
||||
finally:
|
||||
@@ -127,57 +61,65 @@ class Lock(_ContextManagerMixin):
|
||||
|
||||
lock = Lock()
|
||||
...
|
||||
with (yield from lock):
|
||||
async with lock:
|
||||
...
|
||||
|
||||
Lock objects can be tested for locking state:
|
||||
|
||||
if not lock.locked():
|
||||
yield from lock
|
||||
await lock.acquire()
|
||||
else:
|
||||
# lock is acquired
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *, loop=None):
|
||||
self._waiters = collections.deque()
|
||||
def __init__(self):
|
||||
self._waiters = None
|
||||
self._locked = False
|
||||
if loop is not None:
|
||||
self._loop = loop
|
||||
else:
|
||||
self._loop = events.get_event_loop()
|
||||
|
||||
def __repr__(self):
|
||||
res = super().__repr__()
|
||||
extra = 'locked' if self._locked else 'unlocked'
|
||||
if self._waiters:
|
||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
||||
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||
return f'<{res[1:-1]} [{extra}]>'
|
||||
|
||||
def locked(self):
|
||||
"""Return True if lock is acquired."""
|
||||
return self._locked
|
||||
|
||||
@coroutine
|
||||
def acquire(self):
|
||||
async def acquire(self):
|
||||
"""Acquire a lock.
|
||||
|
||||
This method blocks until the lock is unlocked, then sets it to
|
||||
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
|
||||
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)
|
||||
|
||||
# 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:
|
||||
yield from fut
|
||||
self._locked = True
|
||||
return True
|
||||
finally:
|
||||
self._waiters.remove(fut)
|
||||
try:
|
||||
await fut
|
||||
finally:
|
||||
self._waiters.remove(fut)
|
||||
except exceptions.CancelledError:
|
||||
if not self._locked:
|
||||
self._wake_up_first()
|
||||
raise
|
||||
|
||||
self._locked = True
|
||||
return True
|
||||
|
||||
def release(self):
|
||||
"""Release a lock.
|
||||
@@ -192,16 +134,27 @@ class Lock(_ContextManagerMixin):
|
||||
"""
|
||||
if self._locked:
|
||||
self._locked = False
|
||||
# Wake up the first waiter who isn't cancelled.
|
||||
for fut in self._waiters:
|
||||
if not fut.done():
|
||||
fut.set_result(True)
|
||||
break
|
||||
self._wake_up_first()
|
||||
else:
|
||||
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.
|
||||
|
||||
Class implementing event objects. An event manages a flag that can be set
|
||||
@@ -210,20 +163,16 @@ class Event:
|
||||
false.
|
||||
"""
|
||||
|
||||
def __init__(self, *, loop=None):
|
||||
def __init__(self):
|
||||
self._waiters = collections.deque()
|
||||
self._value = False
|
||||
if loop is not None:
|
||||
self._loop = loop
|
||||
else:
|
||||
self._loop = events.get_event_loop()
|
||||
|
||||
def __repr__(self):
|
||||
res = super().__repr__()
|
||||
extra = 'set' if self._value else 'unset'
|
||||
if self._waiters:
|
||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
||||
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||
return f'<{res[1:-1]} [{extra}]>'
|
||||
|
||||
def is_set(self):
|
||||
"""Return True if and only if the internal flag is true."""
|
||||
@@ -247,8 +196,7 @@ class Event:
|
||||
to true again."""
|
||||
self._value = False
|
||||
|
||||
@coroutine
|
||||
def wait(self):
|
||||
async def wait(self):
|
||||
"""Block until the internal flag is true.
|
||||
|
||||
If the internal flag is true on entry, return True
|
||||
@@ -258,16 +206,16 @@ class Event:
|
||||
if self._value:
|
||||
return True
|
||||
|
||||
fut = self._loop.create_future()
|
||||
fut = self._get_loop().create_future()
|
||||
self._waiters.append(fut)
|
||||
try:
|
||||
yield from fut
|
||||
await fut
|
||||
return True
|
||||
finally:
|
||||
self._waiters.remove(fut)
|
||||
|
||||
|
||||
class Condition(_ContextManagerMixin):
|
||||
class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
|
||||
"""Asynchronous equivalent to threading.Condition.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, lock=None, *, loop=None):
|
||||
if loop is not None:
|
||||
self._loop = loop
|
||||
else:
|
||||
self._loop = events.get_event_loop()
|
||||
|
||||
def __init__(self, lock=None):
|
||||
if lock is None:
|
||||
lock = Lock(loop=self._loop)
|
||||
elif lock._loop is not self._loop:
|
||||
raise ValueError("loop argument must agree with lock")
|
||||
lock = Lock()
|
||||
|
||||
self._lock = lock
|
||||
# Export the lock's locked(), acquire() and release() methods.
|
||||
@@ -300,11 +241,10 @@ class Condition(_ContextManagerMixin):
|
||||
res = super().__repr__()
|
||||
extra = 'locked' if self.locked() else 'unlocked'
|
||||
if self._waiters:
|
||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
||||
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||
return f'<{res[1:-1]} [{extra}]>'
|
||||
|
||||
@coroutine
|
||||
def wait(self):
|
||||
async def wait(self):
|
||||
"""Wait until notified.
|
||||
|
||||
If the calling coroutine has not acquired the lock when this
|
||||
@@ -320,25 +260,28 @@ class Condition(_ContextManagerMixin):
|
||||
|
||||
self.release()
|
||||
try:
|
||||
fut = self._loop.create_future()
|
||||
fut = self._get_loop().create_future()
|
||||
self._waiters.append(fut)
|
||||
try:
|
||||
yield from fut
|
||||
await fut
|
||||
return True
|
||||
finally:
|
||||
self._waiters.remove(fut)
|
||||
|
||||
finally:
|
||||
# Must reacquire lock even if wait is cancelled
|
||||
cancelled = False
|
||||
while True:
|
||||
try:
|
||||
yield from self.acquire()
|
||||
await self.acquire()
|
||||
break
|
||||
except futures.CancelledError:
|
||||
pass
|
||||
except exceptions.CancelledError:
|
||||
cancelled = True
|
||||
|
||||
@coroutine
|
||||
def wait_for(self, predicate):
|
||||
if cancelled:
|
||||
raise exceptions.CancelledError
|
||||
|
||||
async def wait_for(self, predicate):
|
||||
"""Wait until a predicate becomes true.
|
||||
|
||||
The predicate should be a callable which result will be
|
||||
@@ -347,7 +290,7 @@ class Condition(_ContextManagerMixin):
|
||||
"""
|
||||
result = predicate()
|
||||
while not result:
|
||||
yield from self.wait()
|
||||
await self.wait()
|
||||
result = predicate()
|
||||
return result
|
||||
|
||||
@@ -384,7 +327,7 @@ class Condition(_ContextManagerMixin):
|
||||
self.notify(len(self._waiters))
|
||||
|
||||
|
||||
class Semaphore(_ContextManagerMixin):
|
||||
class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin):
|
||||
"""A Semaphore implementation.
|
||||
|
||||
A semaphore manages an internal counter which is decremented by each
|
||||
@@ -399,37 +342,25 @@ class Semaphore(_ContextManagerMixin):
|
||||
ValueError is raised.
|
||||
"""
|
||||
|
||||
def __init__(self, value=1, *, loop=None):
|
||||
def __init__(self, value=1):
|
||||
if value < 0:
|
||||
raise ValueError("Semaphore initial value must be >= 0")
|
||||
self._waiters = None
|
||||
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):
|
||||
res = super().__repr__()
|
||||
extra = 'locked' if self.locked() else 'unlocked,value:{}'.format(
|
||||
self._value)
|
||||
extra = 'locked' if self.locked() else f'unlocked, value:{self._value}'
|
||||
if self._waiters:
|
||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
||||
return '<{} [{}]>'.format(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
|
||||
extra = f'{extra}, waiters:{len(self._waiters)}'
|
||||
return f'<{res[1:-1]} [{extra}]>'
|
||||
|
||||
def locked(self):
|
||||
"""Returns True if semaphore can not be acquired immediately."""
|
||||
return self._value == 0
|
||||
"""Returns True if semaphore cannot be acquired immediately."""
|
||||
return self._value == 0 or (
|
||||
any(not w.cancelled() for w in (self._waiters or ())))
|
||||
|
||||
@coroutine
|
||||
def acquire(self):
|
||||
async def acquire(self):
|
||||
"""Acquire a semaphore.
|
||||
|
||||
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
|
||||
True.
|
||||
"""
|
||||
while self._value <= 0:
|
||||
fut = self._loop.create_future()
|
||||
self._waiters.append(fut)
|
||||
if not self.locked():
|
||||
self._value -= 1
|
||||
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:
|
||||
yield from fut
|
||||
except:
|
||||
# See the similar code in Queue.get.
|
||||
fut.cancel()
|
||||
if self._value > 0 and not fut.cancelled():
|
||||
self._wake_up_next()
|
||||
raise
|
||||
self._value -= 1
|
||||
await fut
|
||||
finally:
|
||||
self._waiters.remove(fut)
|
||||
except exceptions.CancelledError:
|
||||
if not fut.cancelled():
|
||||
self._value += 1
|
||||
self._wake_up_next()
|
||||
raise
|
||||
|
||||
if self._value > 0:
|
||||
self._wake_up_next()
|
||||
return True
|
||||
|
||||
def release(self):
|
||||
"""Release a semaphore, incrementing the internal counter by one.
|
||||
|
||||
When it was zero on entry and another coroutine is waiting for it to
|
||||
become larger than zero again, wake up that coroutine.
|
||||
"""
|
||||
self._value += 1
|
||||
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):
|
||||
"""A bounded semaphore implementation.
|
||||
@@ -468,11 +424,163 @@ class BoundedSemaphore(Semaphore):
|
||||
above the initial value.
|
||||
"""
|
||||
|
||||
def __init__(self, value=1, *, loop=None):
|
||||
def __init__(self, value=1):
|
||||
self._bound_value = value
|
||||
super().__init__(value, loop=loop)
|
||||
super().__init__(value)
|
||||
|
||||
def release(self):
|
||||
if self._value >= self._bound_value:
|
||||
raise ValueError('BoundedSemaphore released too many times')
|
||||
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.
|
||||
"""
|
||||
|
||||
__all__ = ['BaseProactorEventLoop']
|
||||
__all__ = 'BaseProactorEventLoop',
|
||||
|
||||
import io
|
||||
import os
|
||||
import socket
|
||||
import warnings
|
||||
import signal
|
||||
import threading
|
||||
import collections
|
||||
|
||||
from . import base_events
|
||||
from . import compat
|
||||
from . import constants
|
||||
from . import futures
|
||||
from . import exceptions
|
||||
from . import protocols
|
||||
from . import sslproto
|
||||
from . import transports
|
||||
from . import trsock
|
||||
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,
|
||||
transports.BaseTransport):
|
||||
"""Base class for pipe and socket transports."""
|
||||
@@ -27,7 +52,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
super().__init__(extra, loop)
|
||||
self._set_extra(sock)
|
||||
self._sock = sock
|
||||
self._protocol = protocol
|
||||
self.set_protocol(protocol)
|
||||
self._server = server
|
||||
self._buffer = None # None or bytearray.
|
||||
self._read_fut = None
|
||||
@@ -35,6 +60,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
self._pending_write = 0
|
||||
self._conn_lost = 0
|
||||
self._closing = False # Set when close() called.
|
||||
self._called_connection_lost = False
|
||||
self._eof_written = False
|
||||
if self._server is not None:
|
||||
self._server._attach()
|
||||
@@ -51,17 +77,16 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
elif self._closing:
|
||||
info.append('closing')
|
||||
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:
|
||||
info.append('read=%s' % self._read_fut)
|
||||
info.append(f'read={self._read_fut!r}')
|
||||
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:
|
||||
bufsize = len(self._buffer)
|
||||
info.append('write_bufsize=%s' % bufsize)
|
||||
info.append(f'write_bufsize={len(self._buffer)}')
|
||||
if self._eof_written:
|
||||
info.append('EOF written')
|
||||
return '<%s>' % ' '.join(info)
|
||||
return '<{}>'.format(' '.join(info))
|
||||
|
||||
def _set_extra(self, sock):
|
||||
self._extra['pipe'] = sock
|
||||
@@ -86,31 +111,33 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
self._read_fut.cancel()
|
||||
self._read_fut = None
|
||||
|
||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
||||
# to the PEP 442.
|
||||
if compat.PY34:
|
||||
def __del__(self):
|
||||
if self._sock is not None:
|
||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
||||
source=self)
|
||||
self.close()
|
||||
def __del__(self, _warn=warnings.warn):
|
||||
if self._sock is not None:
|
||||
_warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
|
||||
self._sock.close()
|
||||
|
||||
def _fatal_error(self, exc, message='Fatal error on pipe transport'):
|
||||
if isinstance(exc, base_events._FATAL_ERROR_IGNORE):
|
||||
if self._loop.get_debug():
|
||||
logger.debug("%r: %s", self, message, exc_info=True)
|
||||
else:
|
||||
self._loop.call_exception_handler({
|
||||
'message': message,
|
||||
'exception': exc,
|
||||
'transport': self,
|
||||
'protocol': self._protocol,
|
||||
})
|
||||
self._force_close(exc)
|
||||
try:
|
||||
if isinstance(exc, OSError):
|
||||
if self._loop.get_debug():
|
||||
logger.debug("%r: %s", self, message, exc_info=True)
|
||||
else:
|
||||
self._loop.call_exception_handler({
|
||||
'message': message,
|
||||
'exception': exc,
|
||||
'transport': self,
|
||||
'protocol': self._protocol,
|
||||
})
|
||||
finally:
|
||||
self._force_close(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
|
||||
self._closing = True
|
||||
self._conn_lost += 1
|
||||
@@ -125,6 +152,8 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
self._loop.call_soon(self._call_connection_lost, exc)
|
||||
|
||||
def _call_connection_lost(self, exc):
|
||||
if self._called_connection_lost:
|
||||
return
|
||||
try:
|
||||
self._protocol.connection_lost(exc)
|
||||
finally:
|
||||
@@ -132,7 +161,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
# end then it may fail with ERROR_NETNAME_DELETED if we
|
||||
# just close our end. First calling shutdown() seems to
|
||||
# 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.close()
|
||||
self._sock = None
|
||||
@@ -140,6 +169,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
||||
if server is not None:
|
||||
server._detach()
|
||||
self._server = None
|
||||
self._called_connection_lost = True
|
||||
|
||||
def get_write_buffer_size(self):
|
||||
size = self._pending_write
|
||||
@@ -153,53 +183,127 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
|
||||
"""Transport for read pipes."""
|
||||
|
||||
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)
|
||||
self._paused = False
|
||||
|
||||
self._data = bytearray(buffer_size)
|
||||
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):
|
||||
if self._closing:
|
||||
raise RuntimeError('Cannot pause_reading() when closing')
|
||||
if self._paused:
|
||||
raise RuntimeError('Already paused')
|
||||
if self._closing or self._paused:
|
||||
return
|
||||
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():
|
||||
logger.debug("%r pauses reading", self)
|
||||
|
||||
def resume_reading(self):
|
||||
if not self._paused:
|
||||
raise RuntimeError('Not paused')
|
||||
self._paused = False
|
||||
if self._closing:
|
||||
if self._closing or not self._paused:
|
||||
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():
|
||||
logger.debug("%r resumes reading", self)
|
||||
|
||||
def _loop_reading(self, fut=None):
|
||||
if self._paused:
|
||||
return
|
||||
data = None
|
||||
def _eof_received(self):
|
||||
if self._loop.get_debug():
|
||||
logger.debug("%r received EOF", self)
|
||||
|
||||
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:
|
||||
if fut is not None:
|
||||
assert self._read_fut is fut or (self._read_fut is None and
|
||||
self._closing)
|
||||
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:
|
||||
# since close() has been called we ignore any read data
|
||||
data = None
|
||||
return
|
||||
|
||||
if data == b'':
|
||||
# we got end-of-file so no need to reschedule a new read
|
||||
return
|
||||
# bpo-33694: buffer_updated() has currently no fast path because of
|
||||
# a data loss issue caused by overlapped WSASend() cancellation.
|
||||
|
||||
# reschedule a new read
|
||||
self._read_fut = self._loop._proactor.recv(self._sock, 4096)
|
||||
if not self._paused:
|
||||
# reschedule a new read
|
||||
self._read_fut = self._loop._proactor.recv_into(self._sock, self._data)
|
||||
except ConnectionAbortedError as exc:
|
||||
if not self._closing:
|
||||
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
||||
@@ -210,32 +314,36 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
|
||||
self._force_close(exc)
|
||||
except OSError as exc:
|
||||
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
||||
except futures.CancelledError:
|
||||
except exceptions.CancelledError:
|
||||
if not self._closing:
|
||||
raise
|
||||
else:
|
||||
self._read_fut.add_done_callback(self._loop_reading)
|
||||
if not self._paused:
|
||||
self._read_fut.add_done_callback(self._loop_reading)
|
||||
finally:
|
||||
if data:
|
||||
self._protocol.data_received(data)
|
||||
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()
|
||||
if length > -1:
|
||||
self._data_received(data, length)
|
||||
|
||||
|
||||
class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
||||
transports.WriteTransport):
|
||||
"""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):
|
||||
if not isinstance(data, (bytes, bytearray, memoryview)):
|
||||
raise TypeError('data argument must be byte-ish (%r)',
|
||||
type(data))
|
||||
raise TypeError(
|
||||
f"data argument must be a bytes-like object, "
|
||||
f"not {type(data).__name__}")
|
||||
if self._eof_written:
|
||||
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:
|
||||
return
|
||||
@@ -267,6 +375,10 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
||||
|
||||
def _loop_writing(self, f=None, data=None):
|
||||
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
|
||||
self._write_fut = None
|
||||
self._pending_write = 0
|
||||
@@ -295,6 +407,8 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
||||
self._maybe_pause_protocol()
|
||||
else:
|
||||
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:
|
||||
self._force_close(exc)
|
||||
except OSError as exc:
|
||||
@@ -309,6 +423,17 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
||||
def abort(self):
|
||||
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):
|
||||
def __init__(self, *args, **kw):
|
||||
@@ -332,6 +457,138 @@ class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
|
||||
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,
|
||||
_ProactorBaseWritePipeTransport,
|
||||
transports.Transport):
|
||||
@@ -349,21 +606,15 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport,
|
||||
transports.Transport):
|
||||
"""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):
|
||||
self._extra['socket'] = 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)
|
||||
_set_socket_extra(self, sock)
|
||||
|
||||
def can_write_eof(self):
|
||||
return True
|
||||
@@ -387,26 +638,35 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
self._accept_futures = {} # socket file descriptor => Future
|
||||
proactor.set_loop(self)
|
||||
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,
|
||||
extra=None, server=None):
|
||||
return _ProactorSocketTransport(self, sock, protocol, waiter,
|
||||
extra, server)
|
||||
|
||||
def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None,
|
||||
*, server_side=False, server_hostname=None,
|
||||
extra=None, server=None):
|
||||
if not sslproto._is_sslproto_available():
|
||||
raise NotImplementedError("Proactor event loop requires Python 3.5"
|
||||
" or newer (ssl.MemoryBIO) to support "
|
||||
"SSL")
|
||||
|
||||
ssl_protocol = sslproto.SSLProtocol(self, protocol, sslcontext, waiter,
|
||||
server_side, server_hostname)
|
||||
def _make_ssl_transport(
|
||||
self, rawsock, protocol, sslcontext, waiter=None,
|
||||
*, server_side=False, server_hostname=None,
|
||||
extra=None, server=None,
|
||||
ssl_handshake_timeout=None,
|
||||
ssl_shutdown_timeout=None):
|
||||
ssl_protocol = sslproto.SSLProtocol(
|
||||
self, protocol, sslcontext, waiter,
|
||||
server_side, server_hostname,
|
||||
ssl_handshake_timeout=ssl_handshake_timeout,
|
||||
ssl_shutdown_timeout=ssl_shutdown_timeout)
|
||||
_ProactorSocketTransport(self, rawsock, ssl_protocol,
|
||||
extra=extra, server=server)
|
||||
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,
|
||||
extra=None):
|
||||
return _ProactorDuplexPipeTransport(self,
|
||||
@@ -428,6 +688,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
if self.is_closed():
|
||||
return
|
||||
|
||||
if threading.current_thread() is threading.main_thread():
|
||||
signal.set_wakeup_fd(-1)
|
||||
# Call these methods before closing the event loop (before calling
|
||||
# BaseEventLoop.close), because they can schedule callbacks with
|
||||
# call_soon(), which is forbidden when the event loop is closed.
|
||||
@@ -440,20 +702,73 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
# Close the event loop
|
||||
super().close()
|
||||
|
||||
def sock_recv(self, sock, n):
|
||||
return self._proactor.recv(sock, n)
|
||||
async def sock_recv(self, sock, n):
|
||||
return await self._proactor.recv(sock, n)
|
||||
|
||||
def sock_sendall(self, sock, data):
|
||||
return self._proactor.send(sock, data)
|
||||
async def sock_recv_into(self, sock, buf):
|
||||
return await self._proactor.recv_into(sock, buf)
|
||||
|
||||
def sock_connect(self, sock, address):
|
||||
return self._proactor.connect(sock, address)
|
||||
async def sock_recvfrom(self, sock, bufsize):
|
||||
return await self._proactor.recvfrom(sock, bufsize)
|
||||
|
||||
def sock_accept(self, sock):
|
||||
return self._proactor.accept(sock)
|
||||
async def sock_recvfrom_into(self, sock, buf, nbytes=0):
|
||||
if not nbytes:
|
||||
nbytes = len(buf)
|
||||
|
||||
def _socketpair(self):
|
||||
raise NotImplementedError
|
||||
return await self._proactor.recvfrom_into(sock, buf, nbytes)
|
||||
|
||||
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):
|
||||
if self._self_reading_future is not None:
|
||||
@@ -467,21 +782,30 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
|
||||
def _make_self_pipe(self):
|
||||
# A self-socket, really. :-)
|
||||
self._ssock, self._csock = self._socketpair()
|
||||
self._ssock, self._csock = socket.socketpair()
|
||||
self._ssock.setblocking(False)
|
||||
self._csock.setblocking(False)
|
||||
self._internal_fds += 1
|
||||
self.call_soon(self._loop_self_reading)
|
||||
|
||||
def _loop_self_reading(self, f=None):
|
||||
try:
|
||||
if f is not None:
|
||||
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)
|
||||
except futures.CancelledError:
|
||||
except exceptions.CancelledError:
|
||||
# _close_self_pipe() has been called, stop waiting for data
|
||||
return
|
||||
except Exception as exc:
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
self.call_exception_handler({
|
||||
'message': 'Error on reading from the event loop self pipe',
|
||||
'exception': exc,
|
||||
@@ -492,10 +816,27 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
f.add_done_callback(self._loop_self_reading)
|
||||
|
||||
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,
|
||||
sslcontext=None, server=None, backlog=100):
|
||||
sslcontext=None, server=None, backlog=100,
|
||||
ssl_handshake_timeout=None,
|
||||
ssl_shutdown_timeout=None):
|
||||
|
||||
def loop(f=None):
|
||||
try:
|
||||
@@ -508,7 +849,9 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
if sslcontext is not None:
|
||||
self._make_ssl_transport(
|
||||
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:
|
||||
self._make_socket_transport(
|
||||
conn, protocol,
|
||||
@@ -521,13 +864,13 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
self.call_exception_handler({
|
||||
'message': 'Accept failed on a socket',
|
||||
'exception': exc,
|
||||
'socket': sock,
|
||||
'socket': trsock.TransportSocket(sock),
|
||||
})
|
||||
sock.close()
|
||||
elif self._debug:
|
||||
logger.debug("Accept failed on socket %r",
|
||||
sock, exc_info=True)
|
||||
except futures.CancelledError:
|
||||
except exceptions.CancelledError:
|
||||
sock.close()
|
||||
else:
|
||||
self._accept_futures[sock.fileno()] = f
|
||||
@@ -545,6 +888,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||
self._accept_futures.clear()
|
||||
|
||||
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)
|
||||
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',
|
||||
'SubprocessProtocol']
|
||||
__all__ = (
|
||||
'BaseProtocol', 'Protocol', 'DatagramProtocol',
|
||||
'SubprocessProtocol', 'BufferedProtocol',
|
||||
)
|
||||
|
||||
|
||||
class BaseProtocol:
|
||||
@@ -14,6 +16,8 @@ class BaseProtocol:
|
||||
write-only transport like write pipe
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""Called when a connection is made.
|
||||
|
||||
@@ -85,6 +89,8 @@ class Protocol(BaseProtocol):
|
||||
* CL: connection_lost()
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def data_received(self, data):
|
||||
"""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):
|
||||
"""Interface for datagram protocol."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def datagram_received(self, data, addr):
|
||||
"""Called when some datagram is received."""
|
||||
|
||||
@@ -116,6 +177,8 @@ class DatagramProtocol(BaseProtocol):
|
||||
class SubprocessProtocol(BaseProtocol):
|
||||
"""Interface for protocol for subprocess calls."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def pipe_data_received(self, fd, data):
|
||||
"""Called when the subprocess writes data into stdout/stderr pipe.
|
||||
|
||||
@@ -132,3 +195,22 @@ class SubprocessProtocol(BaseProtocol):
|
||||
|
||||
def process_exited(self):
|
||||
"""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 heapq
|
||||
from types import GenericAlias
|
||||
|
||||
from . import compat
|
||||
from . import events
|
||||
from . import locks
|
||||
from .coroutines import coroutine
|
||||
from . import mixins
|
||||
|
||||
|
||||
class QueueEmpty(Exception):
|
||||
"""Exception raised when Queue.get_nowait() is called on a Queue object
|
||||
which is empty.
|
||||
"""
|
||||
"""Raised when Queue.get_nowait() is called on an empty Queue."""
|
||||
pass
|
||||
|
||||
|
||||
class QueueFull(Exception):
|
||||
"""Exception raised when the Queue.put_nowait() method is called on a Queue
|
||||
object which is full.
|
||||
"""
|
||||
"""Raised when the Queue.put_nowait() method is called on a full Queue."""
|
||||
pass
|
||||
|
||||
|
||||
class Queue:
|
||||
class Queue(mixins._LoopBoundMixin):
|
||||
"""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
|
||||
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().
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, maxsize=0, *, loop=None):
|
||||
if loop is None:
|
||||
self._loop = events.get_event_loop()
|
||||
else:
|
||||
self._loop = loop
|
||||
def __init__(self, maxsize=0):
|
||||
self._maxsize = maxsize
|
||||
|
||||
# Futures.
|
||||
@@ -49,7 +38,7 @@ class Queue:
|
||||
# Futures.
|
||||
self._putters = collections.deque()
|
||||
self._unfinished_tasks = 0
|
||||
self._finished = locks.Event(loop=self._loop)
|
||||
self._finished = locks.Event()
|
||||
self._finished.set()
|
||||
self._init(maxsize)
|
||||
|
||||
@@ -75,25 +64,23 @@ class Queue:
|
||||
break
|
||||
|
||||
def __repr__(self):
|
||||
return '<{} at {:#x} {}>'.format(
|
||||
type(self).__name__, id(self), self._format())
|
||||
return f'<{type(self).__name__} at {id(self):#x} {self._format()}>'
|
||||
|
||||
def __str__(self):
|
||||
return '<{} {}>'.format(type(self).__name__, self._format())
|
||||
return f'<{type(self).__name__} {self._format()}>'
|
||||
|
||||
def __class_getitem__(cls, type):
|
||||
return cls
|
||||
__class_getitem__ = classmethod(GenericAlias)
|
||||
|
||||
def _format(self):
|
||||
result = 'maxsize={!r}'.format(self._maxsize)
|
||||
result = f'maxsize={self._maxsize!r}'
|
||||
if getattr(self, '_queue', None):
|
||||
result += ' _queue={!r}'.format(list(self._queue))
|
||||
result += f' _queue={list(self._queue)!r}'
|
||||
if self._getters:
|
||||
result += ' _getters[{}]'.format(len(self._getters))
|
||||
result += f' _getters[{len(self._getters)}]'
|
||||
if self._putters:
|
||||
result += ' _putters[{}]'.format(len(self._putters))
|
||||
result += f' _putters[{len(self._putters)}]'
|
||||
if self._unfinished_tasks:
|
||||
result += ' tasks={}'.format(self._unfinished_tasks)
|
||||
result += f' tasks={self._unfinished_tasks}'
|
||||
return result
|
||||
|
||||
def qsize(self):
|
||||
@@ -120,22 +107,26 @@ class Queue:
|
||||
else:
|
||||
return self.qsize() >= self._maxsize
|
||||
|
||||
@coroutine
|
||||
def put(self, item):
|
||||
async def put(self, item):
|
||||
"""Put an item into the queue.
|
||||
|
||||
Put an item into the queue. If the queue is full, wait until a free
|
||||
slot is available before adding item.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
while self.full():
|
||||
putter = self._loop.create_future()
|
||||
putter = self._get_loop().create_future()
|
||||
self._putters.append(putter)
|
||||
try:
|
||||
yield from putter
|
||||
await putter
|
||||
except:
|
||||
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():
|
||||
# We were woken up by get_nowait(), but can't take
|
||||
# the call. Wake up the next in line.
|
||||
@@ -155,21 +146,25 @@ class Queue:
|
||||
self._finished.clear()
|
||||
self._wakeup_next(self._getters)
|
||||
|
||||
@coroutine
|
||||
def get(self):
|
||||
async def get(self):
|
||||
"""Remove and return an item from the queue.
|
||||
|
||||
If queue is empty, wait until an item is available.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
while self.empty():
|
||||
getter = self._loop.create_future()
|
||||
getter = self._get_loop().create_future()
|
||||
self._getters.append(getter)
|
||||
try:
|
||||
yield from getter
|
||||
await getter
|
||||
except:
|
||||
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():
|
||||
# We were woken up by put_nowait(), but can't take
|
||||
# the call. Wake up the next in line.
|
||||
@@ -208,8 +203,7 @@ class Queue:
|
||||
if self._unfinished_tasks == 0:
|
||||
self._finished.set()
|
||||
|
||||
@coroutine
|
||||
def join(self):
|
||||
async def join(self):
|
||||
"""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
|
||||
@@ -218,7 +212,7 @@ class Queue:
|
||||
When the count of unfinished tasks drops to zero, join() unblocks.
|
||||
"""
|
||||
if self._unfinished_tasks > 0:
|
||||
yield from self._finished.wait()
|
||||
await self._finished.wait()
|
||||
|
||||
|
||||
class PriorityQueue(Queue):
|
||||
@@ -248,9 +242,3 @@ class LifoQueue(Queue):
|
||||
|
||||
def _get(self):
|
||||
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 events
|
||||
from . import exceptions
|
||||
from . import tasks
|
||||
from . import constants
|
||||
|
||||
class _State(enum.Enum):
|
||||
CREATED = "created"
|
||||
INITIALIZED = "initialized"
|
||||
CLOSED = "closed"
|
||||
|
||||
|
||||
def run(main, *, debug=False):
|
||||
"""Run a coroutine.
|
||||
class Runner:
|
||||
"""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
|
||||
managing the asyncio event loop and finalizing asynchronous
|
||||
generators.
|
||||
managing the asyncio event loop, finalizing asynchronous
|
||||
generators and closing the default executor.
|
||||
|
||||
This function cannot be called when another asyncio event loop is
|
||||
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
|
||||
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:
|
||||
|
||||
async def main():
|
||||
@@ -30,24 +186,12 @@ def run(main, *, debug=False):
|
||||
asyncio.run(main())
|
||||
"""
|
||||
if events._get_running_loop() is not None:
|
||||
# fail fast with short traceback
|
||||
raise RuntimeError(
|
||||
"asyncio.run() cannot be called from a running event loop")
|
||||
|
||||
if not coroutines.iscoroutine(main):
|
||||
raise ValueError("a coroutine was expected, got {!r}".format(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()
|
||||
with Runner(debug=debug, loop_factory=loop_factory) as runner:
|
||||
return runner.run(main)
|
||||
|
||||
|
||||
def _cancel_all_tasks(loop):
|
||||
@@ -58,8 +202,7 @@ def _cancel_all_tasks(loop):
|
||||
for task in to_cancel:
|
||||
task.cancel()
|
||||
|
||||
loop.run_until_complete(
|
||||
tasks.gather(*to_cancel, loop=loop, return_exceptions=True))
|
||||
loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
|
||||
|
||||
for task in to_cancel:
|
||||
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',
|
||||
'open_connection', 'start_server',
|
||||
'IncompleteReadError',
|
||||
'LimitOverrunError',
|
||||
]
|
||||
__all__ = (
|
||||
'StreamReader', 'StreamWriter', 'StreamReaderProtocol',
|
||||
'open_connection', 'start_server')
|
||||
|
||||
import collections
|
||||
import socket
|
||||
import sys
|
||||
import warnings
|
||||
import weakref
|
||||
|
||||
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 compat
|
||||
from . import events
|
||||
from . import exceptions
|
||||
from . import format_helpers
|
||||
from . import protocols
|
||||
from .coroutines import coroutine
|
||||
from .log import logger
|
||||
from .tasks import sleep
|
||||
|
||||
|
||||
_DEFAULT_LIMIT = 2 ** 16
|
||||
_DEFAULT_LIMIT = 2 ** 16 # 64 KiB
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
async def open_connection(host=None, port=None, *,
|
||||
limit=_DEFAULT_LIMIT, **kwds):
|
||||
"""A wrapper for create_connection() returning a (reader, writer) pair.
|
||||
|
||||
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
|
||||
really nothing special here except some convenience.)
|
||||
"""
|
||||
if loop is None:
|
||||
loop = events.get_event_loop()
|
||||
loop = events.get_running_loop()
|
||||
reader = StreamReader(limit=limit, loop=loop)
|
||||
protocol = StreamReaderProtocol(reader, loop=loop)
|
||||
transport, _ = yield from loop.create_connection(
|
||||
transport, _ = await loop.create_connection(
|
||||
lambda: protocol, host, port, **kwds)
|
||||
writer = StreamWriter(transport, protocol, reader, loop)
|
||||
return reader, writer
|
||||
|
||||
|
||||
@coroutine
|
||||
def start_server(client_connected_cb, host=None, port=None, *,
|
||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
||||
async def start_server(client_connected_cb, host=None, port=None, *,
|
||||
limit=_DEFAULT_LIMIT, **kwds):
|
||||
"""Start a socket server, call back for each client connected.
|
||||
|
||||
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
|
||||
following. The return value is the same as loop.create_server().
|
||||
|
||||
Additional optional keyword arguments are loop (to set the event loop
|
||||
instance to use) and limit (to set the buffer limit passed to the
|
||||
StreamReader).
|
||||
Additional optional keyword argument is limit (to set the buffer
|
||||
limit passed to the StreamReader).
|
||||
|
||||
The return value is the same as loop.create_server(), i.e. a
|
||||
Server object which can be used to stop the service.
|
||||
"""
|
||||
if loop is None:
|
||||
loop = events.get_event_loop()
|
||||
loop = events.get_running_loop()
|
||||
|
||||
def factory():
|
||||
reader = StreamReader(limit=limit, loop=loop)
|
||||
@@ -110,31 +81,28 @@ def start_server(client_connected_cb, host=None, port=None, *,
|
||||
loop=loop)
|
||||
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'):
|
||||
# UNIX Domain Sockets are supported on this platform
|
||||
|
||||
@coroutine
|
||||
def open_unix_connection(path=None, *,
|
||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
||||
async def open_unix_connection(path=None, *,
|
||||
limit=_DEFAULT_LIMIT, **kwds):
|
||||
"""Similar to `open_connection` but works with UNIX Domain Sockets."""
|
||||
if loop is None:
|
||||
loop = events.get_event_loop()
|
||||
loop = events.get_running_loop()
|
||||
|
||||
reader = StreamReader(limit=limit, loop=loop)
|
||||
protocol = StreamReaderProtocol(reader, loop=loop)
|
||||
transport, _ = yield from loop.create_unix_connection(
|
||||
transport, _ = await loop.create_unix_connection(
|
||||
lambda: protocol, path, **kwds)
|
||||
writer = StreamWriter(transport, protocol, reader, loop)
|
||||
return reader, writer
|
||||
|
||||
@coroutine
|
||||
def start_unix_server(client_connected_cb, path=None, *,
|
||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
||||
async def start_unix_server(client_connected_cb, path=None, *,
|
||||
limit=_DEFAULT_LIMIT, **kwds):
|
||||
"""Similar to `start_server` but works with UNIX Domain Sockets."""
|
||||
if loop is None:
|
||||
loop = events.get_event_loop()
|
||||
loop = events.get_running_loop()
|
||||
|
||||
def factory():
|
||||
reader = StreamReader(limit=limit, loop=loop)
|
||||
@@ -142,14 +110,14 @@ if hasattr(socket, 'AF_UNIX'):
|
||||
loop=loop)
|
||||
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):
|
||||
"""Reusable flow control logic for StreamWriter.drain().
|
||||
|
||||
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.
|
||||
|
||||
StreamWriter.drain() must wait for _drain_helper() coroutine.
|
||||
@@ -161,7 +129,7 @@ class FlowControlMixin(protocols.Protocol):
|
||||
else:
|
||||
self._loop = loop
|
||||
self._paused = False
|
||||
self._drain_waiter = None
|
||||
self._drain_waiters = collections.deque()
|
||||
self._connection_lost = False
|
||||
|
||||
def pause_writing(self):
|
||||
@@ -176,39 +144,37 @@ class FlowControlMixin(protocols.Protocol):
|
||||
if self._loop.get_debug():
|
||||
logger.debug("%r resumes writing", self)
|
||||
|
||||
waiter = self._drain_waiter
|
||||
if waiter is not None:
|
||||
self._drain_waiter = None
|
||||
for waiter in self._drain_waiters:
|
||||
if not waiter.done():
|
||||
waiter.set_result(None)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self._connection_lost = True
|
||||
# Wake up the writer if currently paused.
|
||||
# Wake up the writer(s) if currently paused.
|
||||
if not self._paused:
|
||||
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
|
||||
def _drain_helper(self):
|
||||
for waiter in self._drain_waiters:
|
||||
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:
|
||||
raise ConnectionResetError('Connection lost')
|
||||
if not self._paused:
|
||||
return
|
||||
waiter = self._drain_waiter
|
||||
assert waiter is None or waiter.cancelled()
|
||||
waiter = self._loop.create_future()
|
||||
self._drain_waiter = waiter
|
||||
yield from waiter
|
||||
self._drain_waiters.append(waiter)
|
||||
try:
|
||||
await waiter
|
||||
finally:
|
||||
self._drain_waiters.remove(waiter)
|
||||
|
||||
def _get_close_waiter(self, stream):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
||||
@@ -220,40 +186,110 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
||||
call inappropriate methods of the protocol.)
|
||||
"""
|
||||
|
||||
_source_traceback = None
|
||||
|
||||
def __init__(self, stream_reader, client_connected_cb=None, loop=None):
|
||||
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._task = None
|
||||
self._transport = None
|
||||
self._client_connected_cb = client_connected_cb
|
||||
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):
|
||||
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
|
||||
if self._client_connected_cb is not None:
|
||||
self._stream_writer = StreamWriter(transport, self,
|
||||
self._stream_reader,
|
||||
reader,
|
||||
self._loop)
|
||||
res = self._client_connected_cb(self._stream_reader,
|
||||
res = self._client_connected_cb(reader,
|
||||
self._stream_writer)
|
||||
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):
|
||||
if self._stream_reader is not None:
|
||||
reader = self._stream_reader
|
||||
if reader is not None:
|
||||
if exc is None:
|
||||
self._stream_reader.feed_eof()
|
||||
reader.feed_eof()
|
||||
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)
|
||||
self._stream_reader = None
|
||||
self._stream_reader_wr = None
|
||||
self._stream_writer = None
|
||||
self._task = None
|
||||
self._transport = None
|
||||
|
||||
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):
|
||||
self._stream_reader.feed_eof()
|
||||
reader = self._stream_reader
|
||||
if reader is not None:
|
||||
reader.feed_eof()
|
||||
if self._over_ssl:
|
||||
# Prevent a warning in SSLProtocol.eof_received:
|
||||
# "returning true from eof_received()
|
||||
@@ -261,6 +297,20 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
||||
return False
|
||||
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:
|
||||
"""Wraps a Transport.
|
||||
@@ -279,12 +329,14 @@ class StreamWriter:
|
||||
assert reader is None or isinstance(reader, StreamReader)
|
||||
self._reader = reader
|
||||
self._loop = loop
|
||||
self._complete_fut = self._loop.create_future()
|
||||
self._complete_fut.set_result(None)
|
||||
|
||||
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:
|
||||
info.append('reader=%r' % self._reader)
|
||||
return '<%s>' % ' '.join(info)
|
||||
info.append(f'reader={self._reader!r}')
|
||||
return '<{}>'.format(' '.join(info))
|
||||
|
||||
@property
|
||||
def transport(self):
|
||||
@@ -305,36 +357,68 @@ class StreamWriter:
|
||||
def close(self):
|
||||
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):
|
||||
return self._transport.get_extra_info(name, default)
|
||||
|
||||
@coroutine
|
||||
def drain(self):
|
||||
async def drain(self):
|
||||
"""Flush the write buffer.
|
||||
|
||||
The intended use is to write
|
||||
|
||||
w.write(data)
|
||||
yield from w.drain()
|
||||
await w.drain()
|
||||
"""
|
||||
if self._reader is not None:
|
||||
exc = self._reader.exception()
|
||||
if exc is not None:
|
||||
raise exc
|
||||
if self._transport is not None:
|
||||
if self._transport.is_closing():
|
||||
# Yield to the event loop so connection_lost() may be
|
||||
# called. Without this, _drain_helper() would return
|
||||
# immediately, and code that calls
|
||||
# write(...); yield from drain()
|
||||
# in a loop would never call connection_lost(), so it
|
||||
# would not see an error when the socket is closed.
|
||||
yield
|
||||
yield from self._protocol._drain_helper()
|
||||
if self._transport.is_closing():
|
||||
# Wait for protocol.connection_lost() call
|
||||
# Raise connection closing error if any,
|
||||
# ConnectionResetError otherwise
|
||||
# Yield to the event loop so connection_lost() may be
|
||||
# called. Without this, _drain_helper() would return
|
||||
# immediately, and code that calls
|
||||
# write(...); await drain()
|
||||
# in a loop would never call connection_lost(), so it
|
||||
# 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:
|
||||
|
||||
_source_traceback = None
|
||||
|
||||
def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
|
||||
# The line length limit is a security feature;
|
||||
# it also doubles as half the buffer limit.
|
||||
@@ -353,24 +437,27 @@ class StreamReader:
|
||||
self._exception = None
|
||||
self._transport = None
|
||||
self._paused = False
|
||||
if self._loop.get_debug():
|
||||
self._source_traceback = format_helpers.extract_stack(
|
||||
sys._getframe(1))
|
||||
|
||||
def __repr__(self):
|
||||
info = ['StreamReader']
|
||||
if self._buffer:
|
||||
info.append('%d bytes' % len(self._buffer))
|
||||
info.append(f'{len(self._buffer)} bytes')
|
||||
if self._eof:
|
||||
info.append('eof')
|
||||
if self._limit != _DEFAULT_LIMIT:
|
||||
info.append('l=%d' % self._limit)
|
||||
info.append(f'limit={self._limit}')
|
||||
if self._waiter:
|
||||
info.append('w=%r' % self._waiter)
|
||||
info.append(f'waiter={self._waiter!r}')
|
||||
if self._exception:
|
||||
info.append('e=%r' % self._exception)
|
||||
info.append(f'exception={self._exception!r}')
|
||||
if self._transport:
|
||||
info.append('t=%r' % self._transport)
|
||||
info.append(f'transport={self._transport!r}')
|
||||
if self._paused:
|
||||
info.append('paused')
|
||||
return '<%s>' % ' '.join(info)
|
||||
return '<{}>'.format(' '.join(info))
|
||||
|
||||
def exception(self):
|
||||
return self._exception
|
||||
@@ -431,8 +518,7 @@ class StreamReader:
|
||||
else:
|
||||
self._paused = True
|
||||
|
||||
@coroutine
|
||||
def _wait_for_data(self, func_name):
|
||||
async def _wait_for_data(self, func_name):
|
||||
"""Wait until feed_data() or feed_eof() is called.
|
||||
|
||||
If stream was paused, automatically resume it.
|
||||
@@ -442,8 +528,9 @@ class StreamReader:
|
||||
# would have an unexpected behaviour. It would not possible to know
|
||||
# which coroutine would get the next data.
|
||||
if self._waiter is not None:
|
||||
raise RuntimeError('%s() called while another coroutine is '
|
||||
'already waiting for incoming data' % func_name)
|
||||
raise RuntimeError(
|
||||
f'{func_name}() called while another coroutine is '
|
||||
f'already waiting for incoming data')
|
||||
|
||||
assert not self._eof, '_wait_for_data after EOF'
|
||||
|
||||
@@ -455,12 +542,11 @@ class StreamReader:
|
||||
|
||||
self._waiter = self._loop.create_future()
|
||||
try:
|
||||
yield from self._waiter
|
||||
await self._waiter
|
||||
finally:
|
||||
self._waiter = None
|
||||
|
||||
@coroutine
|
||||
def readline(self):
|
||||
async def readline(self):
|
||||
"""Read chunk of data from the stream until newline (b'\n') is found.
|
||||
|
||||
On success, return chunk that ends with newline. If only partial
|
||||
@@ -479,10 +565,10 @@ class StreamReader:
|
||||
sep = b'\n'
|
||||
seplen = len(sep)
|
||||
try:
|
||||
line = yield from self.readuntil(sep)
|
||||
except IncompleteReadError as e:
|
||||
line = await self.readuntil(sep)
|
||||
except exceptions.IncompleteReadError as e:
|
||||
return e.partial
|
||||
except LimitOverrunError as e:
|
||||
except exceptions.LimitOverrunError as e:
|
||||
if self._buffer.startswith(sep, e.consumed):
|
||||
del self._buffer[:e.consumed + seplen]
|
||||
else:
|
||||
@@ -491,8 +577,7 @@ class StreamReader:
|
||||
raise ValueError(e.args[0])
|
||||
return line
|
||||
|
||||
@coroutine
|
||||
def readuntil(self, separator=b'\n'):
|
||||
async def readuntil(self, separator=b'\n'):
|
||||
"""Read data from the stream until ``separator`` is found.
|
||||
|
||||
On success, the data and separator will be removed from the
|
||||
@@ -558,7 +643,7 @@ class StreamReader:
|
||||
# see upper comment for explanation.
|
||||
offset = buflen + 1 - seplen
|
||||
if offset > self._limit:
|
||||
raise LimitOverrunError(
|
||||
raise exceptions.LimitOverrunError(
|
||||
'Separator is not found, and chunk exceed the limit',
|
||||
offset)
|
||||
|
||||
@@ -569,13 +654,13 @@ class StreamReader:
|
||||
if self._eof:
|
||||
chunk = bytes(self._buffer)
|
||||
self._buffer.clear()
|
||||
raise IncompleteReadError(chunk, None)
|
||||
raise exceptions.IncompleteReadError(chunk, None)
|
||||
|
||||
# _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:
|
||||
raise LimitOverrunError(
|
||||
raise exceptions.LimitOverrunError(
|
||||
'Separator is found, but chunk is longer than limit', isep)
|
||||
|
||||
chunk = self._buffer[:isep + seplen]
|
||||
@@ -583,20 +668,20 @@ class StreamReader:
|
||||
self._maybe_resume_transport()
|
||||
return bytes(chunk)
|
||||
|
||||
@coroutine
|
||||
def read(self, n=-1):
|
||||
async def read(self, n=-1):
|
||||
"""Read up to `n` bytes from the stream.
|
||||
|
||||
If n is not provided, or set to -1, read until EOF and return all read
|
||||
bytes. If the EOF was received and the internal buffer is empty, return
|
||||
an empty bytes object.
|
||||
If `n` is not provided or set to -1,
|
||||
read until EOF, then return all read bytes.
|
||||
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
|
||||
less or equal bytes than requested, but at least one byte. If EOF was
|
||||
received before any byte is read, this function returns empty byte
|
||||
object.
|
||||
If `n` is positive, return at most `n` available bytes
|
||||
as soon as at least 1 byte is available in the internal buffer.
|
||||
If EOF is received before any byte is read, return an empty
|
||||
bytes object.
|
||||
|
||||
Returned value is not limited with limit, configured at stream
|
||||
creation.
|
||||
@@ -618,24 +703,23 @@ class StreamReader:
|
||||
# bytes. So just call self.read(self._limit) until EOF.
|
||||
blocks = []
|
||||
while True:
|
||||
block = yield from self.read(self._limit)
|
||||
block = await self.read(self._limit)
|
||||
if not block:
|
||||
break
|
||||
blocks.append(block)
|
||||
return b''.join(blocks)
|
||||
|
||||
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
|
||||
data = bytes(self._buffer[:n])
|
||||
data = bytes(memoryview(self._buffer)[:n])
|
||||
del self._buffer[:n]
|
||||
|
||||
self._maybe_resume_transport()
|
||||
return data
|
||||
|
||||
@coroutine
|
||||
def readexactly(self, n):
|
||||
async def readexactly(self, n):
|
||||
"""Read exactly `n` bytes.
|
||||
|
||||
Raise an IncompleteReadError if EOF is reached before `n` bytes can be
|
||||
@@ -663,33 +747,24 @@ class StreamReader:
|
||||
if self._eof:
|
||||
incomplete = bytes(self._buffer)
|
||||
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:
|
||||
data = bytes(self._buffer)
|
||||
self._buffer.clear()
|
||||
else:
|
||||
data = bytes(self._buffer[:n])
|
||||
data = bytes(memoryview(self._buffer)[:n])
|
||||
del self._buffer[:n]
|
||||
self._maybe_resume_transport()
|
||||
return data
|
||||
|
||||
if compat.PY35:
|
||||
@coroutine
|
||||
def __aiter__(self):
|
||||
return self
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
@coroutine
|
||||
def __anext__(self):
|
||||
val = yield from self.readline()
|
||||
if val == b'':
|
||||
raise StopAsyncIteration
|
||||
return val
|
||||
|
||||
if compat.PY352:
|
||||
# In Python 3.5.2 and greater, __aiter__ should return
|
||||
# the asynchronous iterator directly.
|
||||
def __aiter__(self):
|
||||
return self
|
||||
async def __anext__(self):
|
||||
val = await self.readline()
|
||||
if val == b'':
|
||||
raise StopAsyncIteration
|
||||
return val
|
||||
|
||||
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
|
||||
|
||||
@@ -6,7 +6,6 @@ from . import events
|
||||
from . import protocols
|
||||
from . import streams
|
||||
from . import tasks
|
||||
from .coroutines import coroutine
|
||||
from .log import logger
|
||||
|
||||
|
||||
@@ -24,16 +23,19 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
||||
self._limit = limit
|
||||
self.stdin = self.stdout = self.stderr = None
|
||||
self._transport = None
|
||||
self._process_exited = False
|
||||
self._pipe_fds = []
|
||||
self._stdin_closed = self._loop.create_future()
|
||||
|
||||
def __repr__(self):
|
||||
info = [self.__class__.__name__]
|
||||
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:
|
||||
info.append('stdout=%r' % self.stdout)
|
||||
info.append(f'stdout={self.stdout!r}')
|
||||
if self.stderr is not None:
|
||||
info.append('stderr=%r' % self.stderr)
|
||||
return '<%s>' % ' '.join(info)
|
||||
info.append(f'stderr={self.stderr!r}')
|
||||
return '<{}>'.format(' '.join(info))
|
||||
|
||||
def connection_made(self, transport):
|
||||
self._transport = transport
|
||||
@@ -43,12 +45,14 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
||||
self.stdout = streams.StreamReader(limit=self._limit,
|
||||
loop=self._loop)
|
||||
self.stdout.set_transport(stdout_transport)
|
||||
self._pipe_fds.append(1)
|
||||
|
||||
stderr_transport = transport.get_pipe_transport(2)
|
||||
if stderr_transport is not None:
|
||||
self.stderr = streams.StreamReader(limit=self._limit,
|
||||
loop=self._loop)
|
||||
self.stderr.set_transport(stderr_transport)
|
||||
self._pipe_fds.append(2)
|
||||
|
||||
stdin_transport = transport.get_pipe_transport(0)
|
||||
if stdin_transport is not None:
|
||||
@@ -73,6 +77,13 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
||||
if pipe is not None:
|
||||
pipe.close()
|
||||
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
|
||||
if fd == 1:
|
||||
reader = self.stdout
|
||||
@@ -80,15 +91,28 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
|
||||
reader = self.stderr
|
||||
else:
|
||||
reader = None
|
||||
if reader != None:
|
||||
if reader is not None:
|
||||
if exc is None:
|
||||
reader.feed_eof()
|
||||
else:
|
||||
reader.set_exception(exc)
|
||||
|
||||
if fd in self._pipe_fds:
|
||||
self._pipe_fds.remove(fd)
|
||||
self._maybe_close_transport()
|
||||
|
||||
def process_exited(self):
|
||||
self._transport.close()
|
||||
self._transport = None
|
||||
self._process_exited = True
|
||||
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:
|
||||
@@ -102,18 +126,15 @@ class Process:
|
||||
self.pid = transport.get_pid()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s>' % (self.__class__.__name__, self.pid)
|
||||
return f'<{self.__class__.__name__} {self.pid}>'
|
||||
|
||||
@property
|
||||
def returncode(self):
|
||||
return self._transport.get_returncode()
|
||||
|
||||
@coroutine
|
||||
def wait(self):
|
||||
"""Wait until the process exit and return the process return code.
|
||||
|
||||
This method is a coroutine."""
|
||||
return (yield from self._transport._wait())
|
||||
async def wait(self):
|
||||
"""Wait until the process exit and return the process return code."""
|
||||
return await self._transport._wait()
|
||||
|
||||
def send_signal(self, signal):
|
||||
self._transport.send_signal(signal)
|
||||
@@ -124,17 +145,19 @@ class Process:
|
||||
def kill(self):
|
||||
self._transport.kill()
|
||||
|
||||
@coroutine
|
||||
def _feed_stdin(self, input):
|
||||
async def _feed_stdin(self, input):
|
||||
debug = self._loop.get_debug()
|
||||
self.stdin.write(input)
|
||||
if debug:
|
||||
logger.debug('%r communicate: feed stdin (%s bytes)',
|
||||
self, len(input))
|
||||
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:
|
||||
# communicate() ignores BrokenPipeError and ConnectionResetError
|
||||
# communicate() ignores BrokenPipeError and ConnectionResetError.
|
||||
# write() and drain() can raise these exceptions.
|
||||
if debug:
|
||||
logger.debug('%r communicate: stdin got %r', self, exc)
|
||||
|
||||
@@ -142,12 +165,10 @@ class Process:
|
||||
logger.debug('%r communicate: close stdin', self)
|
||||
self.stdin.close()
|
||||
|
||||
@coroutine
|
||||
def _noop(self):
|
||||
async def _noop(self):
|
||||
return None
|
||||
|
||||
@coroutine
|
||||
def _read_stream(self, fd):
|
||||
async def _read_stream(self, fd):
|
||||
transport = self._transport.get_pipe_transport(fd)
|
||||
if fd == 2:
|
||||
stream = self.stderr
|
||||
@@ -157,16 +178,15 @@ class Process:
|
||||
if self._loop.get_debug():
|
||||
name = 'stdout' if fd == 1 else 'stderr'
|
||||
logger.debug('%r communicate: read %s', self, name)
|
||||
output = yield from stream.read()
|
||||
output = await stream.read()
|
||||
if self._loop.get_debug():
|
||||
name = 'stdout' if fd == 1 else 'stderr'
|
||||
logger.debug('%r communicate: close %s', self, name)
|
||||
transport.close()
|
||||
return output
|
||||
|
||||
@coroutine
|
||||
def communicate(self, input=None):
|
||||
if input is not None:
|
||||
async def communicate(self, input=None):
|
||||
if self.stdin is not None:
|
||||
stdin = self._feed_stdin(input)
|
||||
else:
|
||||
stdin = self._noop()
|
||||
@@ -178,36 +198,32 @@ class Process:
|
||||
stderr = self._read_stream(2)
|
||||
else:
|
||||
stderr = self._noop()
|
||||
stdin, stdout, stderr = yield from tasks.gather(stdin, stdout, stderr,
|
||||
loop=self._loop)
|
||||
yield from self.wait()
|
||||
stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr)
|
||||
await self.wait()
|
||||
return (stdout, stderr)
|
||||
|
||||
|
||||
@coroutine
|
||||
def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
|
||||
loop=None, limit=streams._DEFAULT_LIMIT, **kwds):
|
||||
if loop is None:
|
||||
loop = events.get_event_loop()
|
||||
async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
|
||||
limit=streams._DEFAULT_LIMIT, **kwds):
|
||||
loop = events.get_running_loop()
|
||||
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
||||
loop=loop)
|
||||
transport, protocol = yield from loop.subprocess_shell(
|
||||
protocol_factory,
|
||||
cmd, stdin=stdin, stdout=stdout,
|
||||
stderr=stderr, **kwds)
|
||||
transport, protocol = await loop.subprocess_shell(
|
||||
protocol_factory,
|
||||
cmd, stdin=stdin, stdout=stdout,
|
||||
stderr=stderr, **kwds)
|
||||
return Process(transport, protocol, loop)
|
||||
|
||||
@coroutine
|
||||
def create_subprocess_exec(program, *args, stdin=None, stdout=None,
|
||||
stderr=None, loop=None,
|
||||
limit=streams._DEFAULT_LIMIT, **kwds):
|
||||
if loop is None:
|
||||
loop = events.get_event_loop()
|
||||
|
||||
async def create_subprocess_exec(program, *args, stdin=None, stdout=None,
|
||||
stderr=None, limit=streams._DEFAULT_LIMIT,
|
||||
**kwds):
|
||||
loop = events.get_running_loop()
|
||||
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
||||
loop=loop)
|
||||
transport, protocol = yield from loop.subprocess_exec(
|
||||
protocol_factory,
|
||||
program, *args,
|
||||
stdin=stdin, stdout=stdout,
|
||||
stderr=stderr, **kwds)
|
||||
transport, protocol = await loop.subprocess_exec(
|
||||
protocol_factory,
|
||||
program, *args,
|
||||
stdin=stdin, stdout=stdout,
|
||||
stderr=stderr, **kwds)
|
||||
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."""
|
||||
|
||||
from asyncio import compat
|
||||
|
||||
__all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport',
|
||||
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
||||
]
|
||||
__all__ = (
|
||||
'BaseTransport', 'ReadTransport', 'WriteTransport',
|
||||
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
||||
)
|
||||
|
||||
|
||||
class BaseTransport:
|
||||
"""Base class for transports."""
|
||||
|
||||
__slots__ = ('_extra',)
|
||||
|
||||
def __init__(self, extra=None):
|
||||
if extra is None:
|
||||
extra = {}
|
||||
@@ -28,8 +29,8 @@ class BaseTransport:
|
||||
|
||||
Buffered data will be flushed asynchronously. No more data
|
||||
will be received. After all buffered data is flushed, the
|
||||
protocol's connection_lost() method will (eventually) called
|
||||
with None as its argument.
|
||||
protocol's connection_lost() method will (eventually) be
|
||||
called with None as its argument.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -45,6 +46,12 @@ class BaseTransport:
|
||||
class ReadTransport(BaseTransport):
|
||||
"""Interface for read-only transports."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def is_reading(self):
|
||||
"""Return True if the transport is receiving."""
|
||||
raise NotImplementedError
|
||||
|
||||
def pause_reading(self):
|
||||
"""Pause the receiving end.
|
||||
|
||||
@@ -65,6 +72,8 @@ class ReadTransport(BaseTransport):
|
||||
class WriteTransport(BaseTransport):
|
||||
"""Interface for write-only transports."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def set_write_buffer_limits(self, high=None, low=None):
|
||||
"""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."""
|
||||
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):
|
||||
"""Write some data bytes to the transport.
|
||||
|
||||
@@ -104,7 +119,7 @@ class WriteTransport(BaseTransport):
|
||||
The default implementation concatenates the arguments and
|
||||
calls write() on the result.
|
||||
"""
|
||||
data = compat.flatten_list_bytes(list_of_data)
|
||||
data = b''.join(list_of_data)
|
||||
self.write(data)
|
||||
|
||||
def write_eof(self):
|
||||
@@ -151,10 +166,14 @@ class Transport(ReadTransport, WriteTransport):
|
||||
except writelines(), which calls write() in a loop.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class DatagramTransport(BaseTransport):
|
||||
"""Interface for datagram (UDP) transports."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def sendto(self, data, addr=None):
|
||||
"""Send data to the transport.
|
||||
|
||||
@@ -177,6 +196,8 @@ class DatagramTransport(BaseTransport):
|
||||
|
||||
class SubprocessTransport(BaseTransport):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def get_pid(self):
|
||||
"""Get subprocess id."""
|
||||
raise NotImplementedError
|
||||
@@ -244,6 +265,8 @@ class _FlowControlMixin(Transport):
|
||||
resume_writing() may be called.
|
||||
"""
|
||||
|
||||
__slots__ = ('_loop', '_protocol_paused', '_high_water', '_low_water')
|
||||
|
||||
def __init__(self, extra=None, loop=None):
|
||||
super().__init__(extra)
|
||||
assert loop is not None
|
||||
@@ -259,7 +282,9 @@ class _FlowControlMixin(Transport):
|
||||
self._protocol_paused = True
|
||||
try:
|
||||
self._protocol.pause_writing()
|
||||
except Exception as exc:
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
self._loop.call_exception_handler({
|
||||
'message': 'protocol.pause_writing() failed',
|
||||
'exception': exc,
|
||||
@@ -269,11 +294,13 @@ class _FlowControlMixin(Transport):
|
||||
|
||||
def _maybe_resume_protocol(self):
|
||||
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
|
||||
try:
|
||||
self._protocol.resume_writing()
|
||||
except Exception as exc:
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
self._loop.call_exception_handler({
|
||||
'message': 'protocol.resume_writing() failed',
|
||||
'exception': exc,
|
||||
@@ -287,14 +314,16 @@ class _FlowControlMixin(Transport):
|
||||
def _set_write_buffer_limits(self, high=None, low=None):
|
||||
if high is None:
|
||||
if low is None:
|
||||
high = 64*1024
|
||||
high = 64 * 1024
|
||||
else:
|
||||
high = 4*low
|
||||
high = 4 * low
|
||||
if low is None:
|
||||
low = high // 4
|
||||
|
||||
if not high >= low >= 0:
|
||||
raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
|
||||
(high, low))
|
||||
raise ValueError(
|
||||
f'high ({high!r}) must be >= low ({low!r}) must be >= 0')
|
||||
|
||||
self._high_water = high
|
||||
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."""
|
||||
|
||||
import sys
|
||||
|
||||
if sys.platform != 'win32': # pragma: no cover
|
||||
raise ImportError('win32 only')
|
||||
|
||||
import _overlapped
|
||||
import _winapi
|
||||
import errno
|
||||
from functools import partial
|
||||
import math
|
||||
import msvcrt
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import weakref
|
||||
|
||||
from . import events
|
||||
from . import base_subprocess
|
||||
from . import futures
|
||||
from . import exceptions
|
||||
from . import proactor_events
|
||||
from . import selector_events
|
||||
from . import tasks
|
||||
from . import windows_utils
|
||||
# XXX RustPython TODO: _overlapped
|
||||
# from . import _overlapped
|
||||
from .coroutines import coroutine
|
||||
from .log import logger
|
||||
|
||||
|
||||
__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
|
||||
'DefaultEventLoopPolicy',
|
||||
]
|
||||
__all__ = (
|
||||
'SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
|
||||
'DefaultEventLoopPolicy', 'WindowsSelectorEventLoopPolicy',
|
||||
'WindowsProactorEventLoopPolicy',
|
||||
)
|
||||
|
||||
|
||||
NULL = 0
|
||||
INFINITE = 0xffffffff
|
||||
NULL = _winapi.NULL
|
||||
INFINITE = _winapi.INFINITE
|
||||
ERROR_CONNECTION_REFUSED = 1225
|
||||
ERROR_CONNECTION_ABORTED = 1236
|
||||
|
||||
@@ -53,7 +62,7 @@ class _OverlappedFuture(futures.Future):
|
||||
info = super()._repr_info()
|
||||
if self._ov is not None:
|
||||
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
|
||||
|
||||
def _cancel_overlapped(self):
|
||||
@@ -72,9 +81,9 @@ class _OverlappedFuture(futures.Future):
|
||||
self._loop.call_exception_handler(context)
|
||||
self._ov = None
|
||||
|
||||
def cancel(self):
|
||||
def cancel(self, msg=None):
|
||||
self._cancel_overlapped()
|
||||
return super().cancel()
|
||||
return super().cancel(msg=msg)
|
||||
|
||||
def set_exception(self, exception):
|
||||
super().set_exception(exception)
|
||||
@@ -109,12 +118,12 @@ class _BaseWaitHandleFuture(futures.Future):
|
||||
|
||||
def _repr_info(self):
|
||||
info = super()._repr_info()
|
||||
info.append('handle=%#x' % self._handle)
|
||||
info.append(f'handle={self._handle:#x}')
|
||||
if self._handle is not None:
|
||||
state = 'signaled' if self._poll() else 'waiting'
|
||||
info.append(state)
|
||||
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
|
||||
|
||||
def _unregister_wait_cb(self, fut):
|
||||
@@ -146,9 +155,9 @@ class _BaseWaitHandleFuture(futures.Future):
|
||||
|
||||
self._unregister_wait_cb(None)
|
||||
|
||||
def cancel(self):
|
||||
def cancel(self, msg=None):
|
||||
self._unregister_wait()
|
||||
return super().cancel()
|
||||
return super().cancel(msg=msg)
|
||||
|
||||
def set_exception(self, exception):
|
||||
self._unregister_wait()
|
||||
@@ -297,9 +306,6 @@ class PipeServer(object):
|
||||
class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||
"""Windows version of selector event loop."""
|
||||
|
||||
def _socketpair(self):
|
||||
return windows_utils.socketpair()
|
||||
|
||||
|
||||
class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||
"""Windows version of proactor event loop using IOCP."""
|
||||
@@ -309,20 +315,34 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||
proactor = IocpProactor()
|
||||
super().__init__(proactor)
|
||||
|
||||
def _socketpair(self):
|
||||
return windows_utils.socketpair()
|
||||
def run_forever(self):
|
||||
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
|
||||
def create_pipe_connection(self, protocol_factory, address):
|
||||
async def create_pipe_connection(self, protocol_factory, address):
|
||||
f = self._proactor.connect_pipe(address)
|
||||
pipe = yield from f
|
||||
pipe = await f
|
||||
protocol = protocol_factory()
|
||||
trans = self._make_duplex_pipe_transport(pipe, protocol,
|
||||
extra={'addr': address})
|
||||
return trans, protocol
|
||||
|
||||
@coroutine
|
||||
def start_serving_pipe(self, protocol_factory, address):
|
||||
async def start_serving_pipe(self, protocol_factory, address):
|
||||
server = PipeServer(address)
|
||||
|
||||
def loop_accept_pipe(f=None):
|
||||
@@ -347,6 +367,10 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||
return
|
||||
|
||||
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:
|
||||
if pipe and pipe.fileno() != -1:
|
||||
self.call_exception_handler({
|
||||
@@ -358,7 +382,8 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||
elif self._debug:
|
||||
logger.warning("Accept pipe failed on pipe %r",
|
||||
pipe, exc_info=True)
|
||||
except futures.CancelledError:
|
||||
self.call_soon(loop_accept_pipe)
|
||||
except exceptions.CancelledError:
|
||||
if pipe:
|
||||
pipe.close()
|
||||
else:
|
||||
@@ -368,28 +393,22 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||
self.call_soon(loop_accept_pipe)
|
||||
return [server]
|
||||
|
||||
@coroutine
|
||||
def _make_subprocess_transport(self, protocol, args, shell,
|
||||
stdin, stdout, stderr, bufsize,
|
||||
extra=None, **kwargs):
|
||||
async def _make_subprocess_transport(self, protocol, args, shell,
|
||||
stdin, stdout, stderr, bufsize,
|
||||
extra=None, **kwargs):
|
||||
waiter = self.create_future()
|
||||
transp = _WindowsSubprocessTransport(self, protocol, args, shell,
|
||||
stdin, stdout, stderr, bufsize,
|
||||
waiter=waiter, extra=extra,
|
||||
**kwargs)
|
||||
try:
|
||||
yield from waiter
|
||||
except Exception as exc:
|
||||
# Workaround CPython bug #23353: using yield/yield-from in an
|
||||
# except block of a generator doesn't clear properly sys.exc_info()
|
||||
err = exc
|
||||
else:
|
||||
err = None
|
||||
|
||||
if err is not None:
|
||||
await waiter
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException:
|
||||
transp.close()
|
||||
yield from transp._wait()
|
||||
raise err
|
||||
await transp._wait()
|
||||
raise
|
||||
|
||||
return transp
|
||||
|
||||
@@ -397,7 +416,7 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||
class IocpProactor:
|
||||
"""Proactor implementation using IOCP."""
|
||||
|
||||
def __init__(self, concurrency=0xffffffff):
|
||||
def __init__(self, concurrency=INFINITE):
|
||||
self._loop = None
|
||||
self._results = []
|
||||
self._iocp = _overlapped.CreateIoCompletionPort(
|
||||
@@ -407,10 +426,16 @@ class IocpProactor:
|
||||
self._unregistered = []
|
||||
self._stopped_serving = weakref.WeakSet()
|
||||
|
||||
def _check_closed(self):
|
||||
if self._iocp is None:
|
||||
raise RuntimeError('IocpProactor is closed')
|
||||
|
||||
def __repr__(self):
|
||||
return ('<%s overlapped#=%s result#=%s>'
|
||||
% (self.__class__.__name__, len(self._cache),
|
||||
len(self._results)))
|
||||
info = ['overlapped#=%s' % len(self._cache),
|
||||
'result#=%s' % len(self._results)]
|
||||
if self._iocp is None:
|
||||
info.append('closed')
|
||||
return '<%s %s>' % (self.__class__.__name__, " ".join(info))
|
||||
|
||||
def set_loop(self, loop):
|
||||
self._loop = loop
|
||||
@@ -420,13 +445,40 @@ class IocpProactor:
|
||||
self._poll(timeout)
|
||||
tmp = self._results
|
||||
self._results = []
|
||||
return tmp
|
||||
try:
|
||||
return tmp
|
||||
finally:
|
||||
# Needed to break cycles when an exception occurs.
|
||||
tmp = None
|
||||
|
||||
def _result(self, value):
|
||||
fut = self._loop.create_future()
|
||||
fut.set_result(value)
|
||||
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):
|
||||
self._register_with_iocp(conn)
|
||||
ov = _overlapped.Overlapped(NULL)
|
||||
@@ -438,16 +490,50 @@ class IocpProactor:
|
||||
except BrokenPipeError:
|
||||
return self._result(b'')
|
||||
|
||||
def finish_recv(trans, key, ov):
|
||||
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, self.finish_socket_func)
|
||||
|
||||
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):
|
||||
self._register_with_iocp(conn)
|
||||
@@ -457,16 +543,7 @@ class IocpProactor:
|
||||
else:
|
||||
ov.WriteFile(conn.fileno(), buf)
|
||||
|
||||
def finish_send(trans, key, ov):
|
||||
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)
|
||||
return self._register(ov, conn, self.finish_socket_func)
|
||||
|
||||
def accept(self, listener):
|
||||
self._register_with_iocp(listener)
|
||||
@@ -483,12 +560,11 @@ class IocpProactor:
|
||||
conn.settimeout(listener.gettimeout())
|
||||
return conn, conn.getpeername()
|
||||
|
||||
@coroutine
|
||||
def accept_coro(future, conn):
|
||||
async def accept_coro(future, conn):
|
||||
# Coroutine closing the accept socket if the future is cancelled
|
||||
try:
|
||||
yield from future
|
||||
except futures.CancelledError:
|
||||
await future
|
||||
except exceptions.CancelledError:
|
||||
conn.close()
|
||||
raise
|
||||
|
||||
@@ -498,6 +574,14 @@ class IocpProactor:
|
||||
return future
|
||||
|
||||
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)
|
||||
# The socket needs to be locally bound before we call ConnectEx().
|
||||
try:
|
||||
@@ -520,6 +604,18 @@ class IocpProactor:
|
||||
|
||||
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):
|
||||
self._register_with_iocp(pipe)
|
||||
ov = _overlapped.Overlapped(NULL)
|
||||
@@ -537,13 +633,12 @@ class IocpProactor:
|
||||
|
||||
return self._register(ov, pipe, finish_accept_pipe)
|
||||
|
||||
@coroutine
|
||||
def connect_pipe(self, address):
|
||||
async def connect_pipe(self, address):
|
||||
delay = CONNECT_PIPE_INIT_DELAY
|
||||
while True:
|
||||
# Unfortunately there is no way to do an overlapped connect to a pipe.
|
||||
# Call CreateFile() in a loop until it doesn't fail with
|
||||
# ERROR_PIPE_BUSY
|
||||
# Unfortunately there is no way to do an overlapped connect to
|
||||
# a pipe. Call CreateFile() in a loop until it doesn't fail with
|
||||
# ERROR_PIPE_BUSY.
|
||||
try:
|
||||
handle = _overlapped.ConnectPipe(address)
|
||||
break
|
||||
@@ -553,7 +648,7 @@ class IocpProactor:
|
||||
|
||||
# ConnectPipe() failed with ERROR_PIPE_BUSY: retry later
|
||||
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)
|
||||
|
||||
@@ -573,6 +668,8 @@ class IocpProactor:
|
||||
return fut
|
||||
|
||||
def _wait_for_handle(self, handle, timeout, _is_cancel):
|
||||
self._check_closed()
|
||||
|
||||
if timeout is None:
|
||||
ms = _winapi.INFINITE
|
||||
else:
|
||||
@@ -615,6 +712,8 @@ class IocpProactor:
|
||||
# that succeed immediately.
|
||||
|
||||
def _register(self, ov, obj, callback):
|
||||
self._check_closed()
|
||||
|
||||
# Return a future which will be set with the result of the
|
||||
# operation when it completes. The future's value is actually
|
||||
# the value returned by callback().
|
||||
@@ -651,6 +750,7 @@ class IocpProactor:
|
||||
already be signalled (pending in the proactor event queue). It is also
|
||||
safe if the event is never signalled (because it was cancelled).
|
||||
"""
|
||||
self._check_closed()
|
||||
self._unregistered.append(ov)
|
||||
|
||||
def _get_accept_socket(self, family):
|
||||
@@ -707,8 +807,10 @@ class IocpProactor:
|
||||
else:
|
||||
f.set_result(value)
|
||||
self._results.append(f)
|
||||
finally:
|
||||
f = None
|
||||
|
||||
# Remove unregisted futures
|
||||
# Remove unregistered futures
|
||||
for ov in self._unregistered:
|
||||
self._cache.pop(ov.address, None)
|
||||
self._unregistered.clear()
|
||||
@@ -720,8 +822,12 @@ class IocpProactor:
|
||||
self._stopped_serving.add(obj)
|
||||
|
||||
def close(self):
|
||||
if self._iocp is None:
|
||||
# already closed
|
||||
return
|
||||
|
||||
# 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():
|
||||
# Nothing to do with cancelled futures
|
||||
pass
|
||||
@@ -742,14 +848,25 @@ class IocpProactor:
|
||||
context['source_traceback'] = fut._source_traceback
|
||||
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:
|
||||
if not self._poll(1):
|
||||
logger.debug('taking long time to close proactor')
|
||||
if next_msg <= time.monotonic():
|
||||
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 = []
|
||||
if self._iocp is not None:
|
||||
_winapi.CloseHandle(self._iocp)
|
||||
self._iocp = None
|
||||
|
||||
_winapi.CloseHandle(self._iocp)
|
||||
self._iocp = None
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
@@ -773,8 +890,12 @@ class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
|
||||
SelectorEventLoop = _WindowsSelectorEventLoop
|
||||
|
||||
|
||||
class _WindowsDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
||||
class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
||||
_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
|
||||
|
||||
@@ -11,13 +9,12 @@ import _winapi
|
||||
import itertools
|
||||
import msvcrt
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
|
||||
__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
|
||||
__all__ = 'pipe', 'Popen', 'PIPE', 'PipeHandle'
|
||||
|
||||
|
||||
# Constants/globals
|
||||
@@ -29,61 +26,14 @@ STDOUT = subprocess.STDOUT
|
||||
_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
|
||||
|
||||
|
||||
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
|
||||
"""Like os.pipe() but with overlapped support and using handles not fds."""
|
||||
address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
|
||||
(os.getpid(), next(_mmap_counter)))
|
||||
address = tempfile.mktemp(
|
||||
prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
|
||||
os.getpid(), next(_mmap_counter)))
|
||||
|
||||
if duplex:
|
||||
openmode = _winapi.PIPE_ACCESS_DUPLEX
|
||||
@@ -138,10 +88,10 @@ class PipeHandle:
|
||||
|
||||
def __repr__(self):
|
||||
if self._handle is not None:
|
||||
handle = 'handle=%r' % self._handle
|
||||
handle = f'handle={self._handle!r}'
|
||||
else:
|
||||
handle = 'closed'
|
||||
return '<%s %s>' % (self.__class__.__name__, handle)
|
||||
return f'<{self.__class__.__name__} {handle}>'
|
||||
|
||||
@property
|
||||
def handle(self):
|
||||
@@ -149,7 +99,7 @@ class PipeHandle:
|
||||
|
||||
def fileno(self):
|
||||
if self._handle is None:
|
||||
raise ValueError("I/O operatioon on closed pipe")
|
||||
raise ValueError("I/O operation on closed pipe")
|
||||
return self._handle
|
||||
|
||||
def close(self, *, CloseHandle=_winapi.CloseHandle):
|
||||
@@ -157,10 +107,9 @@ class PipeHandle:
|
||||
CloseHandle(self._handle)
|
||||
self._handle = None
|
||||
|
||||
def __del__(self):
|
||||
def __del__(self, _warn=warnings.warn):
|
||||
if self._handle is not None:
|
||||
warnings.warn("unclosed %r" % self, ResourceWarning,
|
||||
source=self)
|
||||
_warn(f"unclosed {self!r}", ResourceWarning, source=self)
|
||||
self.close()
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
33
Lib/base64.py
vendored
33
Lib/base64.py
vendored
@@ -508,14 +508,8 @@ MAXBINSIZE = (MAXLINESIZE//4)*3
|
||||
|
||||
def encode(input, output):
|
||||
"""Encode a file; input and output are binary files."""
|
||||
while True:
|
||||
s = input.read(MAXBINSIZE)
|
||||
if not s:
|
||||
break
|
||||
while len(s) < MAXBINSIZE:
|
||||
ns = input.read(MAXBINSIZE-len(s))
|
||||
if not ns:
|
||||
break
|
||||
while s := input.read(MAXBINSIZE):
|
||||
while len(s) < MAXBINSIZE and (ns := input.read(MAXBINSIZE-len(s))):
|
||||
s += ns
|
||||
line = binascii.b2a_base64(s)
|
||||
output.write(line)
|
||||
@@ -523,10 +517,7 @@ def encode(input, output):
|
||||
|
||||
def decode(input, output):
|
||||
"""Decode a file; input and output are binary files."""
|
||||
while True:
|
||||
line = input.readline()
|
||||
if not line:
|
||||
break
|
||||
while line := input.readline():
|
||||
s = binascii.a2b_base64(line)
|
||||
output.write(s)
|
||||
|
||||
@@ -567,13 +558,12 @@ def decodebytes(s):
|
||||
def main():
|
||||
"""Small main program"""
|
||||
import sys, getopt
|
||||
usage = """usage: %s [-h|-d|-e|-u|-t] [file|-]
|
||||
usage = f"""usage: {sys.argv[0]} [-h|-d|-e|-u] [file|-]
|
||||
-h: print this help message and exit
|
||||
-d, -u: decode
|
||||
-e: encode (default)
|
||||
-t: encode and decode string 'Aladdin:open sesame'"""%sys.argv[0]
|
||||
-e: encode (default)"""
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hdeut')
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hdeu')
|
||||
except getopt.error as msg:
|
||||
sys.stdout = sys.stderr
|
||||
print(msg)
|
||||
@@ -584,7 +574,6 @@ def main():
|
||||
if o == '-e': func = encode
|
||||
if o == '-d': func = decode
|
||||
if o == '-u': func = decode
|
||||
if o == '-t': test(); return
|
||||
if o == '-h': print(usage); return
|
||||
if args and args[0] != '-':
|
||||
with open(args[0], 'rb') as f:
|
||||
@@ -593,15 +582,5 @@ def main():
|
||||
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__':
|
||||
main()
|
||||
|
||||
26
Lib/bdb.py
vendored
26
Lib/bdb.py
vendored
@@ -570,9 +570,12 @@ class Bdb:
|
||||
rv = frame.f_locals['__return__']
|
||||
s += '->'
|
||||
s += reprlib.repr(rv)
|
||||
line = linecache.getline(filename, lineno, frame.f_globals)
|
||||
if line:
|
||||
s += lprefix + line.strip()
|
||||
if lineno is not None:
|
||||
line = linecache.getline(filename, lineno, frame.f_globals)
|
||||
if line:
|
||||
s += lprefix + line.strip()
|
||||
else:
|
||||
s += f'{lprefix}Warning: lineno is None'
|
||||
return s
|
||||
|
||||
# The following methods can be called by clients to use
|
||||
@@ -805,15 +808,18 @@ def checkfuncname(b, frame):
|
||||
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):
|
||||
"""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 breakpoint that was triggered and a boolean that indicates if it is
|
||||
ok to delete a temporary breakpoint. Return (None, None) if there is no
|
||||
matching breakpoint.
|
||||
The "active breakpoint" is the first entry in bplist[line, file] (which
|
||||
must exist) that is enabled, for which checkfuncname is True, and that
|
||||
has neither a False condition nor a positive ignore count. The flag,
|
||||
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]
|
||||
for b in possibles:
|
||||
|
||||
8
Lib/bisect.py
vendored
8
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
|
||||
slice of a to be searched.
|
||||
|
||||
A custom key function can be supplied to customize the sort order.
|
||||
"""
|
||||
if key is None:
|
||||
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
|
||||
slice of a to be searched.
|
||||
|
||||
A custom key function can be supplied to customize the sort order.
|
||||
"""
|
||||
|
||||
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
|
||||
slice of a to be searched.
|
||||
|
||||
A custom key function can be supplied to customize the sort order.
|
||||
"""
|
||||
|
||||
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
|
||||
slice of a to be searched.
|
||||
|
||||
A custom key function can be supplied to customize the sort order.
|
||||
"""
|
||||
|
||||
if lo < 0:
|
||||
|
||||
76
Lib/calendar.py
vendored
76
Lib/calendar.py
vendored
@@ -7,8 +7,10 @@ set the first day of the week (0=Monday, 6=Sunday)."""
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
from enum import IntEnum, global_enum
|
||||
import locale as _locale
|
||||
from itertools import repeat
|
||||
import warnings
|
||||
|
||||
__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
|
||||
"firstweekday", "isleap", "leapdays", "weekday", "monthrange",
|
||||
@@ -16,6 +18,9 @@ __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
|
||||
"timegm", "month_name", "month_abbr", "day_name", "day_abbr",
|
||||
"Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
|
||||
"LocaleHTMLCalendar", "weekheader",
|
||||
"Day", "Month", "JANUARY", "FEBRUARY", "MARCH",
|
||||
"APRIL", "MAY", "JUNE", "JULY",
|
||||
"AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
|
||||
"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",
|
||||
"SATURDAY", "SUNDAY"]
|
||||
|
||||
@@ -37,9 +42,46 @@ class IllegalWeekdayError(ValueError):
|
||||
return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
|
||||
|
||||
|
||||
# Constants for months referenced later
|
||||
January = 1
|
||||
February = 2
|
||||
def __getattr__(name):
|
||||
if name in ('January', 'February'):
|
||||
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)
|
||||
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
@@ -95,9 +137,6 @@ day_abbr = _localized_day('%a')
|
||||
month_name = _localized_month('%B')
|
||||
month_abbr = _localized_month('%b')
|
||||
|
||||
# Constants for weekdays
|
||||
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
|
||||
|
||||
|
||||
def isleap(year):
|
||||
"""Return True for leap years, False for non-leap years."""
|
||||
@@ -116,7 +155,7 @@ def weekday(year, month, day):
|
||||
"""Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
|
||||
if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
|
||||
year = 2000 + year % 400
|
||||
return datetime.date(year, month, day).weekday()
|
||||
return Day(datetime.date(year, month, day).weekday())
|
||||
|
||||
|
||||
def monthrange(year, month):
|
||||
@@ -125,12 +164,12 @@ def monthrange(year, month):
|
||||
if not 1 <= month <= 12:
|
||||
raise IllegalMonthError(month)
|
||||
day1 = weekday(year, month, 1)
|
||||
ndays = mdays[month] + (month == February and isleap(year))
|
||||
ndays = mdays[month] + (month == FEBRUARY and isleap(year))
|
||||
return day1, ndays
|
||||
|
||||
|
||||
def _monthlen(year, month):
|
||||
return mdays[month] + (month == February and isleap(year))
|
||||
return mdays[month] + (month == FEBRUARY and isleap(year))
|
||||
|
||||
|
||||
def _prevmonth(year, month):
|
||||
@@ -260,10 +299,7 @@ class Calendar(object):
|
||||
Each month contains between 4 and 6 weeks and each week contains 1-7
|
||||
days. Days are datetime.date objects.
|
||||
"""
|
||||
months = [
|
||||
self.monthdatescalendar(year, i)
|
||||
for i in range(January, January+12)
|
||||
]
|
||||
months = [self.monthdatescalendar(year, m) for m in Month]
|
||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
||||
|
||||
def yeardays2calendar(self, year, width=3):
|
||||
@@ -273,10 +309,7 @@ class Calendar(object):
|
||||
(day number, weekday number) tuples. Day numbers outside this month are
|
||||
zero.
|
||||
"""
|
||||
months = [
|
||||
self.monthdays2calendar(year, i)
|
||||
for i in range(January, January+12)
|
||||
]
|
||||
months = [self.monthdays2calendar(year, m) for m in Month]
|
||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
||||
|
||||
def yeardayscalendar(self, year, width=3):
|
||||
@@ -285,10 +318,7 @@ class Calendar(object):
|
||||
yeardatescalendar()). Entries in the week lists are day numbers.
|
||||
Day numbers outside this month are zero.
|
||||
"""
|
||||
months = [
|
||||
self.monthdayscalendar(year, i)
|
||||
for i in range(January, January+12)
|
||||
]
|
||||
months = [self.monthdayscalendar(year, m) for m in Month]
|
||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
||||
|
||||
|
||||
@@ -509,7 +539,7 @@ class HTMLCalendar(Calendar):
|
||||
a('\n')
|
||||
a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
|
||||
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 = range(i, min(i+width, 13))
|
||||
a('<tr>')
|
||||
@@ -693,7 +723,7 @@ def main(args):
|
||||
parser.add_argument(
|
||||
"-L", "--locale",
|
||||
default=None,
|
||||
help="locale to be used from month and weekday names"
|
||||
help="locale to use for month and weekday names"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e", "--encoding",
|
||||
|
||||
2
Lib/cgitb.py
vendored
2
Lib/cgitb.py
vendored
@@ -74,7 +74,7 @@ def lookup(name, frame, locals):
|
||||
return 'global', frame.f_globals[name]
|
||||
if '__builtins__' in frame.f_globals:
|
||||
builtins = frame.f_globals['__builtins__']
|
||||
if type(builtins) is type({}):
|
||||
if isinstance(builtins, dict):
|
||||
if name in builtins:
|
||||
return 'builtin', builtins[name]
|
||||
else:
|
||||
|
||||
4
Lib/code.py
vendored
4
Lib/code.py
vendored
@@ -106,6 +106,7 @@ class InteractiveInterpreter:
|
||||
|
||||
"""
|
||||
type, value, tb = sys.exc_info()
|
||||
sys.last_exc = value
|
||||
sys.last_type = type
|
||||
sys.last_value = value
|
||||
sys.last_traceback = tb
|
||||
@@ -119,7 +120,7 @@ class InteractiveInterpreter:
|
||||
else:
|
||||
# Stuff in the right filename
|
||||
value = SyntaxError(msg, (filename, lineno, offset, line))
|
||||
sys.last_value = value
|
||||
sys.last_exc = sys.last_value = value
|
||||
if sys.excepthook is sys.__excepthook__:
|
||||
lines = traceback.format_exception_only(type, value)
|
||||
self.write(''.join(lines))
|
||||
@@ -138,6 +139,7 @@ class InteractiveInterpreter:
|
||||
"""
|
||||
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
|
||||
sys.last_traceback = last_tb
|
||||
sys.last_exc = ei[1]
|
||||
try:
|
||||
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
|
||||
if sys.excepthook is sys.__excepthook__:
|
||||
|
||||
25
Lib/codecs.py
vendored
25
Lib/codecs.py
vendored
@@ -414,6 +414,9 @@ class StreamWriter(Codec):
|
||||
def __exit__(self, type, value, tb):
|
||||
self.stream.close()
|
||||
|
||||
def __reduce_ex__(self, proto):
|
||||
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||
|
||||
###
|
||||
|
||||
class StreamReader(Codec):
|
||||
@@ -663,6 +666,9 @@ class StreamReader(Codec):
|
||||
def __exit__(self, type, value, tb):
|
||||
self.stream.close()
|
||||
|
||||
def __reduce_ex__(self, proto):
|
||||
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||
|
||||
###
|
||||
|
||||
class StreamReaderWriter:
|
||||
@@ -750,6 +756,9 @@ class StreamReaderWriter:
|
||||
def __exit__(self, type, value, tb):
|
||||
self.stream.close()
|
||||
|
||||
def __reduce_ex__(self, proto):
|
||||
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||
|
||||
###
|
||||
|
||||
class StreamRecoder:
|
||||
@@ -866,6 +875,9 @@ class StreamRecoder:
|
||||
def __exit__(self, type, value, tb):
|
||||
self.stream.close()
|
||||
|
||||
def __reduce_ex__(self, proto):
|
||||
raise TypeError("can't serialize %s" % self.__class__.__name__)
|
||||
|
||||
### Shortcuts
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
encoding specifies the encoding which is to be used for the
|
||||
@@ -1114,13 +1127,3 @@ except LookupError:
|
||||
_false = 0
|
||||
if _false:
|
||||
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')
|
||||
|
||||
20
Lib/codeop.py
vendored
20
Lib/codeop.py
vendored
@@ -70,8 +70,7 @@ def _maybe_compile(compiler, source, filename, symbol):
|
||||
return None
|
||||
# fallthrough
|
||||
|
||||
return compiler(source, filename, symbol)
|
||||
|
||||
return compiler(source, filename, symbol, incomplete_input=False)
|
||||
|
||||
def _is_syntax_error(err1, err2):
|
||||
rep1 = repr(err1)
|
||||
@@ -82,8 +81,13 @@ def _is_syntax_error(err1, err2):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _compile(source, filename, symbol):
|
||||
return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT)
|
||||
def _compile(source, filename, symbol, incomplete_input=True):
|
||||
flags = 0
|
||||
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"):
|
||||
r"""Compile a command and determine whether it is incomplete.
|
||||
@@ -114,8 +118,12 @@ class Compile:
|
||||
def __init__(self):
|
||||
self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT
|
||||
|
||||
def __call__(self, source, filename, symbol):
|
||||
codeob = compile(source, filename, symbol, self.flags, True)
|
||||
def __call__(self, source, filename, symbol, **kwargs):
|
||||
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:
|
||||
if codeob.co_flags & feature.compiler_flag:
|
||||
self.flags |= feature.compiler_flag
|
||||
|
||||
45
Lib/collections/__init__.py
vendored
45
Lib/collections/__init__.py
vendored
@@ -45,6 +45,11 @@ except ImportError:
|
||||
else:
|
||||
_collections_abc.MutableSequence.register(deque)
|
||||
|
||||
try:
|
||||
from _collections import _deque_iterator
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from _collections import defaultdict
|
||||
except ImportError:
|
||||
@@ -94,17 +99,19 @@ class OrderedDict(dict):
|
||||
# Individual links are kept alive by the hard reference in self.__map.
|
||||
# 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):
|
||||
'''Initialize an ordered dictionary. The signature is the same as
|
||||
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)
|
||||
|
||||
def __setitem__(self, key, value,
|
||||
@@ -271,7 +278,7 @@ class OrderedDict(dict):
|
||||
'od.__repr__() <==> repr(od)'
|
||||
if not self:
|
||||
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):
|
||||
'Return state information for pickling'
|
||||
@@ -511,9 +518,12 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
|
||||
# specified a particular module.
|
||||
if module is None:
|
||||
try:
|
||||
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
module = _sys._getframemodulename(1) or '__main__'
|
||||
except AttributeError:
|
||||
try:
|
||||
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is not None:
|
||||
result.__module__ = module
|
||||
|
||||
@@ -1015,8 +1025,8 @@ class ChainMap(_collections_abc.MutableMapping):
|
||||
|
||||
def __iter__(self):
|
||||
d = {}
|
||||
for mapping in reversed(self.maps):
|
||||
d.update(dict.fromkeys(mapping)) # reuses stored hash values if possible
|
||||
for mapping in map(dict.fromkeys, reversed(self.maps)):
|
||||
d |= mapping # reuses stored hash values if possible
|
||||
return iter(d)
|
||||
|
||||
def __contains__(self, key):
|
||||
@@ -1136,10 +1146,17 @@ class UserDict(_collections_abc.MutableMapping):
|
||||
def __iter__(self):
|
||||
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):
|
||||
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
|
||||
def __repr__(self):
|
||||
return repr(self.data)
|
||||
|
||||
2
Lib/colorsys.py
vendored
2
Lib/colorsys.py
vendored
@@ -83,7 +83,7 @@ def rgb_to_hls(r, g, b):
|
||||
if l <= 0.5:
|
||||
s = rangec / sumc
|
||||
else:
|
||||
s = rangec / (2.0-sumc)
|
||||
s = rangec / (2.0-maxc-minc) # Not always 2.0-sumc: gh-106498.
|
||||
rc = (maxc-r) / rangec
|
||||
gc = (maxc-g) / rangec
|
||||
bc = (maxc-b) / rangec
|
||||
|
||||
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
|
||||
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
|
||||
packages -- for now, you'll have to deal with packages separately.)
|
||||
|
||||
@@ -15,16 +15,14 @@ import sys
|
||||
import importlib.util
|
||||
import py_compile
|
||||
import struct
|
||||
import filecmp
|
||||
|
||||
try:
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
except ImportError:
|
||||
ProcessPoolExecutor = None
|
||||
from functools import partial
|
||||
from pathlib import 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):
|
||||
dir = os.fspath(dir)
|
||||
if not quiet:
|
||||
@@ -40,59 +38,94 @@ def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
|
||||
if name == '__pycache__':
|
||||
continue
|
||||
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):
|
||||
yield fullname
|
||||
elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
|
||||
os.path.isdir(fullname) and not os.path.islink(fullname)):
|
||||
yield from _walk_dir(fullname, ddir=dfile,
|
||||
maxlevels=maxlevels - 1, quiet=quiet)
|
||||
yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
|
||||
quiet=quiet)
|
||||
|
||||
def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
|
||||
quiet=0, legacy=False, optimize=-1, workers=1):
|
||||
def compile_dir(dir, maxlevels=None, ddir=None, force=False,
|
||||
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.
|
||||
|
||||
Arguments (only dir is required):
|
||||
|
||||
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
|
||||
file as it is compiled into each byte-code file.
|
||||
force: if True, force compilation, even if timestamps are up-to-date
|
||||
quiet: full output with False or 0, errors only with 1,
|
||||
no output with 2
|
||||
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
|
||||
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')
|
||||
|
||||
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels,
|
||||
ddir=ddir)
|
||||
if workers != 1:
|
||||
# Check if this is a system where ProcessPoolExecutor can function.
|
||||
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
|
||||
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
|
||||
with ProcessPoolExecutor(max_workers=workers) as executor:
|
||||
results = executor.map(partial(compile_file,
|
||||
ddir=ddir, force=force,
|
||||
rx=rx, quiet=quiet,
|
||||
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)
|
||||
success = min(results, default=True)
|
||||
else:
|
||||
for file in files:
|
||||
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
|
||||
return success
|
||||
|
||||
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.
|
||||
|
||||
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,
|
||||
no output with 2
|
||||
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
|
||||
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)
|
||||
|
||||
dfile = None
|
||||
|
||||
if ddir is not None:
|
||||
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:
|
||||
mo = rx.search(fullname)
|
||||
if mo:
|
||||
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 legacy:
|
||||
cfile = fullname + 'c'
|
||||
else:
|
||||
if optimize >= 0:
|
||||
opt = optimize if optimize >= 1 else ''
|
||||
cfile = importlib.util.cache_from_source(
|
||||
fullname, optimization=opt)
|
||||
for opt_level in optimize:
|
||||
if legacy:
|
||||
opt_cfiles[opt_level] = fullname + 'c'
|
||||
else:
|
||||
cfile = importlib.util.cache_from_source(fullname)
|
||||
cache_dir = os.path.dirname(cfile)
|
||||
if opt_level >= 0:
|
||||
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:]
|
||||
if tail == '.py':
|
||||
if not force:
|
||||
try:
|
||||
mtime = int(os.stat(fullname).st_mtime)
|
||||
expect = struct.pack('<4sl', importlib.util.MAGIC_NUMBER,
|
||||
mtime)
|
||||
with open(cfile, 'rb') as chandle:
|
||||
actual = chandle.read(8)
|
||||
if expect == actual:
|
||||
expect = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
|
||||
0, mtime & 0xFFFF_FFFF)
|
||||
for cfile in opt_cfiles.values():
|
||||
with open(cfile, 'rb') as chandle:
|
||||
actual = chandle.read(12)
|
||||
if expect != actual:
|
||||
break
|
||||
else:
|
||||
return success
|
||||
except OSError:
|
||||
pass
|
||||
if not quiet:
|
||||
print('Compiling {!r}...'.format(fullname))
|
||||
try:
|
||||
ok = py_compile.compile(fullname, cfile, dfile, True,
|
||||
optimize=optimize)
|
||||
for index, opt_level in enumerate(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:
|
||||
success = False
|
||||
if quiet >= 2:
|
||||
@@ -156,9 +254,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
||||
else:
|
||||
print('*** ', end='')
|
||||
# escape non-printable characters in msg
|
||||
msg = err.msg.encode(sys.stdout.encoding,
|
||||
errors='backslashreplace')
|
||||
msg = msg.decode(sys.stdout.encoding)
|
||||
encoding = sys.stdout.encoding or sys.getdefaultencoding()
|
||||
msg = err.msg.encode(encoding, errors='backslashreplace').decode(encoding)
|
||||
print(msg)
|
||||
except (SyntaxError, UnicodeError, OSError) as e:
|
||||
success = False
|
||||
@@ -175,7 +272,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
||||
return success
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
legacy: as for compile_dir() (default False)
|
||||
optimize: as for compile_dir() (default -1)
|
||||
invalidation_mode: as for compiler_dir()
|
||||
"""
|
||||
success = True
|
||||
for dir in sys.path:
|
||||
@@ -193,9 +292,16 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
|
||||
if quiet < 2:
|
||||
print('Skipping current directory')
|
||||
else:
|
||||
success = success and compile_dir(dir, maxlevels, None,
|
||||
force, quiet=quiet,
|
||||
legacy=legacy, optimize=optimize)
|
||||
success = success and compile_dir(
|
||||
dir,
|
||||
maxlevels,
|
||||
None,
|
||||
force,
|
||||
quiet=quiet,
|
||||
legacy=legacy,
|
||||
optimize=optimize,
|
||||
invalidation_mode=invalidation_mode,
|
||||
)
|
||||
return success
|
||||
|
||||
|
||||
@@ -206,7 +312,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Utilities to support installing Python libraries.')
|
||||
parser.add_argument('-l', action='store_const', const=0,
|
||||
default=10, dest='maxlevels',
|
||||
default=None, dest='maxlevels',
|
||||
help="don't recurse into subdirectories")
|
||||
parser.add_argument('-r', type=int, dest='recursion',
|
||||
help=('control the maximum recursion level. '
|
||||
@@ -224,6 +330,20 @@ def main():
|
||||
'compile-time tracebacks and in runtime '
|
||||
'tracebacks in cases where the source file is '
|
||||
'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,
|
||||
help=('skip files matching the regular expression; '
|
||||
'the regexp is searched for in the full path '
|
||||
@@ -238,6 +358,23 @@ def main():
|
||||
'to the equivalent of -l sys.path'))
|
||||
parser.add_argument('-j', '--workers', default=1,
|
||||
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()
|
||||
compile_dests = args.compile_dest
|
||||
@@ -246,16 +383,31 @@ def main():
|
||||
import re
|
||||
args.rx = re.compile(args.rx)
|
||||
|
||||
if args.limit_sl_dest == "":
|
||||
args.limit_sl_dest = None
|
||||
|
||||
if args.recursion is not None:
|
||||
maxlevels = args.recursion
|
||||
else:
|
||||
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 args.flist:
|
||||
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:
|
||||
compile_dests.append(line.strip())
|
||||
except OSError:
|
||||
@@ -263,8 +415,11 @@ def main():
|
||||
print("Error reading file list {}".format(args.flist))
|
||||
return False
|
||||
|
||||
if args.workers is not None:
|
||||
args.workers = args.workers or None
|
||||
if args.invalidation_mode:
|
||||
ivl_mode = args.invalidation_mode.replace('-', '_').upper()
|
||||
invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
|
||||
else:
|
||||
invalidation_mode = None
|
||||
|
||||
success = True
|
||||
try:
|
||||
@@ -272,17 +427,30 @@ def main():
|
||||
for dest in compile_dests:
|
||||
if os.path.isfile(dest):
|
||||
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
|
||||
else:
|
||||
if not compile_dir(dest, maxlevels, args.ddir,
|
||||
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
|
||||
return success
|
||||
else:
|
||||
return compile_path(legacy=args.legacy, force=args.force,
|
||||
quiet=args.quiet)
|
||||
quiet=args.quiet,
|
||||
invalidation_mode=invalidation_mode)
|
||||
except KeyboardInterrupt:
|
||||
if args.quiet < 2:
|
||||
print("\n[interrupted]")
|
||||
|
||||
63
Lib/configparser.py
vendored
63
Lib/configparser.py
vendored
@@ -59,7 +59,7 @@ ConfigParser -- responsible for parsing a list of
|
||||
instance. It will be used as the handler for option value
|
||||
pre-processing when using getters. RawConfigParser objects don't do
|
||||
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.
|
||||
|
||||
When `converters` is given, it should be a dictionary where each key
|
||||
@@ -149,14 +149,14 @@ import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
||||
__all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
||||
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
||||
"InterpolationMissingOptionError", "InterpolationSyntaxError",
|
||||
"ParsingError", "MissingSectionHeaderError",
|
||||
"ConfigParser", "SafeConfigParser", "RawConfigParser",
|
||||
"ConfigParser", "RawConfigParser",
|
||||
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
|
||||
"LegacyInterpolation", "SectionProxy", "ConverterMapping",
|
||||
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
|
||||
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH")
|
||||
|
||||
_default_dict = dict
|
||||
DEFAULTSECT = "DEFAULT"
|
||||
@@ -298,41 +298,12 @@ class InterpolationDepthError(InterpolationError):
|
||||
class ParsingError(Error):
|
||||
"""Raised when a configuration file does not follow legal syntax."""
|
||||
|
||||
def __init__(self, source=None, filename=None):
|
||||
# Exactly one of `source'/`filename' arguments has to be given.
|
||||
# `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)
|
||||
def __init__(self, source):
|
||||
super().__init__(f'Source contains parsing errors: {source!r}')
|
||||
self.source = source
|
||||
self.errors = []
|
||||
self.args = (source, )
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
"""Deprecated, use `source'."""
|
||||
warnings.warn(
|
||||
"The 'filename' attribute will be removed in Python 3.12. "
|
||||
"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 Python 3.12. "
|
||||
"Use 'source' instead.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
self.source = value
|
||||
|
||||
def append(self, lineno, line):
|
||||
self.errors.append((lineno, line))
|
||||
self.message += '\n\t[line %2d]: %s' % (lineno, line)
|
||||
@@ -769,15 +740,6 @@ class RawConfigParser(MutableMapping):
|
||||
elements_added.add((section, key))
|
||||
self.set(section, key, value)
|
||||
|
||||
def readfp(self, fp, filename=None):
|
||||
"""Deprecated, use read_file instead."""
|
||||
warnings.warn(
|
||||
"This method will be removed in Python 3.12. "
|
||||
"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):
|
||||
"""Get an option value for a given section.
|
||||
|
||||
@@ -1240,19 +1202,6 @@ class ConfigParser(RawConfigParser):
|
||||
self._interpolation = hold_interpolation
|
||||
|
||||
|
||||
class SafeConfigParser(ConfigParser):
|
||||
"""ConfigParser alias for backwards compatibility purposes."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
warnings.warn(
|
||||
"The SafeConfigParser class has been renamed to ConfigParser "
|
||||
"in Python 3.2. This alias will be removed in Python 3.12."
|
||||
" Use ConfigParser directly instead.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
|
||||
class SectionProxy(MutableMapping):
|
||||
"""A proxy for a single section from a parser."""
|
||||
|
||||
|
||||
35
Lib/contextlib.py
vendored
35
Lib/contextlib.py
vendored
@@ -145,14 +145,17 @@ class _GeneratorContextManager(
|
||||
except StopIteration:
|
||||
return False
|
||||
else:
|
||||
raise RuntimeError("generator didn't stop")
|
||||
try:
|
||||
raise RuntimeError("generator didn't stop")
|
||||
finally:
|
||||
self.gen.close()
|
||||
else:
|
||||
if value is None:
|
||||
# Need to force instantiation so we can reliably
|
||||
# tell if we get the same exception back
|
||||
value = typ()
|
||||
try:
|
||||
self.gen.throw(typ, value, traceback)
|
||||
self.gen.throw(value)
|
||||
except StopIteration as exc:
|
||||
# Suppress StopIteration *unless* it's the same exception that
|
||||
# was passed to throw(). This prevents a StopIteration
|
||||
@@ -187,7 +190,10 @@ class _GeneratorContextManager(
|
||||
raise
|
||||
exc.__traceback__ = traceback
|
||||
return False
|
||||
raise RuntimeError("generator didn't stop after throw()")
|
||||
try:
|
||||
raise RuntimeError("generator didn't stop after throw()")
|
||||
finally:
|
||||
self.gen.close()
|
||||
|
||||
class _AsyncGeneratorContextManager(
|
||||
_GeneratorContextManagerBase,
|
||||
@@ -212,14 +218,17 @@ class _AsyncGeneratorContextManager(
|
||||
except StopAsyncIteration:
|
||||
return False
|
||||
else:
|
||||
raise RuntimeError("generator didn't stop")
|
||||
try:
|
||||
raise RuntimeError("generator didn't stop")
|
||||
finally:
|
||||
await self.gen.aclose()
|
||||
else:
|
||||
if value is None:
|
||||
# Need to force instantiation so we can reliably
|
||||
# tell if we get the same exception back
|
||||
value = typ()
|
||||
try:
|
||||
await self.gen.athrow(typ, value, traceback)
|
||||
await self.gen.athrow(value)
|
||||
except StopAsyncIteration as exc:
|
||||
# Suppress StopIteration *unless* it's the same exception that
|
||||
# was passed to throw(). This prevents a StopIteration
|
||||
@@ -254,7 +263,10 @@ class _AsyncGeneratorContextManager(
|
||||
raise
|
||||
exc.__traceback__ = traceback
|
||||
return False
|
||||
raise RuntimeError("generator didn't stop after athrow()")
|
||||
try:
|
||||
raise RuntimeError("generator didn't stop after athrow()")
|
||||
finally:
|
||||
await self.gen.aclose()
|
||||
|
||||
|
||||
def contextmanager(func):
|
||||
@@ -441,7 +453,16 @@ class suppress(AbstractContextManager):
|
||||
# exactly reproduce the limitations of the CPython interpreter.
|
||||
#
|
||||
# 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:
|
||||
|
||||
28
Lib/copy.py
vendored
28
Lib/copy.py
vendored
@@ -56,11 +56,6 @@ class Error(Exception):
|
||||
pass
|
||||
error = Error # backward compatibility
|
||||
|
||||
try:
|
||||
from org.python.core import PyStringMap
|
||||
except ImportError:
|
||||
PyStringMap = None
|
||||
|
||||
__all__ = ["Error", "copy", "deepcopy"]
|
||||
|
||||
def copy(x):
|
||||
@@ -106,13 +101,11 @@ _copy_dispatch = d = {}
|
||||
|
||||
def _copy_immutable(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,
|
||||
types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
|
||||
types.FunctionType, weakref.ref):
|
||||
d[t] = _copy_immutable
|
||||
t = getattr(types, "CodeType", None)
|
||||
if t is not None:
|
||||
types.BuiltinFunctionType, types.EllipsisType,
|
||||
types.NotImplementedType, types.FunctionType, types.CodeType,
|
||||
weakref.ref):
|
||||
d[t] = _copy_immutable
|
||||
|
||||
d[list] = list.copy
|
||||
@@ -120,9 +113,6 @@ d[dict] = dict.copy
|
||||
d[set] = set.copy
|
||||
d[bytearray] = bytearray.copy
|
||||
|
||||
if PyStringMap is not None:
|
||||
d[PyStringMap] = PyStringMap.copy
|
||||
|
||||
del d, t
|
||||
|
||||
def deepcopy(x, memo=None, _nil=[]):
|
||||
@@ -181,9 +171,9 @@ _deepcopy_dispatch = d = {}
|
||||
|
||||
def _deepcopy_atomic(x, memo):
|
||||
return x
|
||||
d[type(None)] = _deepcopy_atomic
|
||||
d[type(Ellipsis)] = _deepcopy_atomic
|
||||
d[type(NotImplemented)] = _deepcopy_atomic
|
||||
d[types.NoneType] = _deepcopy_atomic
|
||||
d[types.EllipsisType] = _deepcopy_atomic
|
||||
d[types.NotImplementedType] = _deepcopy_atomic
|
||||
d[int] = _deepcopy_atomic
|
||||
d[float] = _deepcopy_atomic
|
||||
d[bool] = _deepcopy_atomic
|
||||
@@ -231,8 +221,6 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy):
|
||||
y[deepcopy(key, memo)] = deepcopy(value, memo)
|
||||
return y
|
||||
d[dict] = _deepcopy_dict
|
||||
if PyStringMap is not None:
|
||||
d[PyStringMap] = _deepcopy_dict
|
||||
|
||||
def _deepcopy_method(x, memo): # Copy instance methods
|
||||
return type(x)(x.__func__, deepcopy(x.__self__, memo))
|
||||
@@ -301,4 +289,4 @@ def _reconstruct(x, memo, func, args,
|
||||
y[key] = value
|
||||
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.
|
||||
|
||||
try:
|
||||
complex
|
||||
except NameError:
|
||||
pass
|
||||
else:
|
||||
def pickle_complex(c):
|
||||
return complex, (c.real, c.imag)
|
||||
|
||||
def pickle_complex(c):
|
||||
return complex, (c.real, c.imag)
|
||||
pickle(complex, pickle_complex, complex)
|
||||
|
||||
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
|
||||
|
||||
@@ -48,6 +48,7 @@ def _reconstructor(cls, base, state):
|
||||
return obj
|
||||
|
||||
_HEAPTYPE = 1<<9
|
||||
_new_type = type(int.__new__)
|
||||
|
||||
# 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__:
|
||||
if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE:
|
||||
break
|
||||
new = base.__new__
|
||||
if isinstance(new, _new_type) and new.__self__ is base:
|
||||
break
|
||||
else:
|
||||
base = object # not really reachable
|
||||
if base is object:
|
||||
@@ -79,6 +83,10 @@ def _reduce_ex(self, proto):
|
||||
except AttributeError:
|
||||
dict = None
|
||||
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()
|
||||
if 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
|
||||
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_STRINGS, QUOTE_NOTNULL, \
|
||||
__doc__
|
||||
from _csv import Dialect as _Dialect
|
||||
|
||||
from collections import OrderedDict
|
||||
from io import StringIO
|
||||
|
||||
__all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
|
||||
"QUOTE_STRINGS", "QUOTE_NOTNULL",
|
||||
"Error", "Dialect", "__doc__", "excel", "excel_tab",
|
||||
"field_size_limit", "reader", "writer",
|
||||
"Sniffer",
|
||||
"register_dialect", "get_dialect", "list_dialects", "Sniffer",
|
||||
"unregister_dialect", "__version__", "DictReader", "DictWriter",
|
||||
"unix_dialect"]
|
||||
|
||||
@@ -57,10 +62,12 @@ class excel(Dialect):
|
||||
skipinitialspace = False
|
||||
lineterminator = '\r\n'
|
||||
quoting = QUOTE_MINIMAL
|
||||
register_dialect("excel", excel)
|
||||
|
||||
class excel_tab(excel):
|
||||
"""Describe the usual properties of Excel-generated TAB-delimited files."""
|
||||
delimiter = '\t'
|
||||
register_dialect("excel-tab", excel_tab)
|
||||
|
||||
class unix_dialect(Dialect):
|
||||
"""Describe the usual properties of Unix-generated CSV files."""
|
||||
@@ -70,11 +77,14 @@ class unix_dialect(Dialect):
|
||||
skipinitialspace = False
|
||||
lineterminator = '\n'
|
||||
quoting = QUOTE_ALL
|
||||
register_dialect("unix", unix_dialect)
|
||||
|
||||
|
||||
class DictReader:
|
||||
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
|
||||
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.restkey = restkey # key to catch long rows
|
||||
self.restval = restval # default value for short rows
|
||||
@@ -111,7 +121,7 @@ class DictReader:
|
||||
# values
|
||||
while row == []:
|
||||
row = next(self.reader)
|
||||
d = OrderedDict(zip(self.fieldnames, row))
|
||||
d = dict(zip(self.fieldnames, row))
|
||||
lf = len(self.fieldnames)
|
||||
lr = len(row)
|
||||
if lf < lr:
|
||||
@@ -121,13 +131,18 @@ class DictReader:
|
||||
d[key] = self.restval
|
||||
return d
|
||||
|
||||
__class_getitem__ = classmethod(types.GenericAlias)
|
||||
|
||||
|
||||
class DictWriter:
|
||||
def __init__(self, f, fieldnames, restval="", extrasaction="raise",
|
||||
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.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'"
|
||||
% extrasaction)
|
||||
self.extrasaction = extrasaction
|
||||
@@ -135,7 +150,7 @@ class DictWriter:
|
||||
|
||||
def writeheader(self):
|
||||
header = dict(zip(self.fieldnames, self.fieldnames))
|
||||
self.writerow(header)
|
||||
return self.writerow(header)
|
||||
|
||||
def _dict_to_list(self, rowdict):
|
||||
if self.extrasaction == "raise":
|
||||
@@ -151,11 +166,8 @@ class DictWriter:
|
||||
def writerows(self, rowdicts):
|
||||
return self.writer.writerows(map(self._dict_to_list, rowdicts))
|
||||
|
||||
# Guard Sniffer's type checking against builds that exclude complex()
|
||||
try:
|
||||
complex
|
||||
except NameError:
|
||||
complex = float
|
||||
__class_getitem__ = classmethod(types.GenericAlias)
|
||||
|
||||
|
||||
class Sniffer:
|
||||
'''
|
||||
@@ -404,14 +416,10 @@ class Sniffer:
|
||||
continue # skip rows that have irregular number of columns
|
||||
|
||||
for col in list(columnTypes.keys()):
|
||||
|
||||
for thisType in [int, float, complex]:
|
||||
try:
|
||||
thisType(row[col])
|
||||
break
|
||||
except (ValueError, OverflowError):
|
||||
pass
|
||||
else:
|
||||
thisType = complex
|
||||
try:
|
||||
thisType(row[col])
|
||||
except (ValueError, OverflowError):
|
||||
# fallback to length of string
|
||||
thisType = len(row[col])
|
||||
|
||||
@@ -427,7 +435,7 @@ class Sniffer:
|
||||
# on whether it's a header
|
||||
hasHeader = 0
|
||||
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:
|
||||
hasHeader += 1
|
||||
else:
|
||||
|
||||
89
Lib/ctypes/test/test_numbers.py
vendored
89
Lib/ctypes/test/test_numbers.py
vendored
@@ -82,14 +82,6 @@ class NumberTestCase(unittest.TestCase):
|
||||
self.assertRaises(TypeError, t, "")
|
||||
self.assertRaises(TypeError, t, None)
|
||||
|
||||
@unittest.skip('test disabled')
|
||||
def test_valid_ranges(self):
|
||||
# invalid values of the correct type
|
||||
# raise ValueError (not OverflowError)
|
||||
for t, (l, h) in zip(unsigned_types, unsigned_ranges):
|
||||
self.assertRaises(ValueError, t, l-1)
|
||||
self.assertRaises(ValueError, t, h+1)
|
||||
|
||||
def test_from_param(self):
|
||||
# the from_param class method attribute always
|
||||
# returns PyCArgObject instances
|
||||
@@ -106,7 +98,7 @@ class NumberTestCase(unittest.TestCase):
|
||||
def test_floats(self):
|
||||
# c_float and c_double can be created from
|
||||
# Python int and float
|
||||
class FloatLike(object):
|
||||
class FloatLike:
|
||||
def __float__(self):
|
||||
return 2.0
|
||||
f = FloatLike()
|
||||
@@ -117,15 +109,15 @@ class NumberTestCase(unittest.TestCase):
|
||||
self.assertEqual(t(f).value, 2.0)
|
||||
|
||||
def test_integers(self):
|
||||
class FloatLike(object):
|
||||
class FloatLike:
|
||||
def __float__(self):
|
||||
return 2.0
|
||||
f = FloatLike()
|
||||
class IntLike(object):
|
||||
class IntLike:
|
||||
def __int__(self):
|
||||
return 2
|
||||
d = IntLike()
|
||||
class IndexLike(object):
|
||||
class IndexLike:
|
||||
def __index__(self):
|
||||
return 2
|
||||
i = IndexLike()
|
||||
@@ -155,10 +147,10 @@ class NumberTestCase(unittest.TestCase):
|
||||
|
||||
# alignment of the type...
|
||||
self.assertEqual((code, alignment(t)),
|
||||
(code, align))
|
||||
(code, align))
|
||||
# and alignment of an instance
|
||||
self.assertEqual((code, alignment(t())),
|
||||
(code, align))
|
||||
(code, align))
|
||||
|
||||
def test_int_from_address(self):
|
||||
from array import array
|
||||
@@ -205,19 +197,6 @@ class NumberTestCase(unittest.TestCase):
|
||||
a[0] = ord('?')
|
||||
self.assertEqual(v.value, b'?')
|
||||
|
||||
# array does not support c_bool / 't'
|
||||
@unittest.skip('test disabled')
|
||||
def test_bool_from_address(self):
|
||||
from ctypes import c_bool
|
||||
from array import array
|
||||
a = array(c_bool._type_, [True])
|
||||
v = t.from_address(a.buffer_info()[0])
|
||||
self.assertEqual(v.value, a[0])
|
||||
self.assertEqual(type(v) is t)
|
||||
a[0] = False
|
||||
self.assertEqual(v.value, a[0])
|
||||
self.assertEqual(type(v) is t)
|
||||
|
||||
def test_init(self):
|
||||
# c_int() can be initialized from Python's int, and c_int.
|
||||
# Not from c_long or so, which seems strange, abc should
|
||||
@@ -234,62 +213,6 @@ class NumberTestCase(unittest.TestCase):
|
||||
if (hasattr(t, "__ctype_le__")):
|
||||
self.assertRaises(OverflowError, t.__ctype_le__, big_int)
|
||||
|
||||
@unittest.skip('test disabled')
|
||||
def test_perf(self):
|
||||
check_perf()
|
||||
|
||||
from ctypes import _SimpleCData
|
||||
class c_int_S(_SimpleCData):
|
||||
_type_ = "i"
|
||||
__slots__ = []
|
||||
|
||||
def run_test(rep, msg, func, arg=None):
|
||||
## items = [None] * rep
|
||||
items = range(rep)
|
||||
from time import perf_counter as clock
|
||||
if arg is not None:
|
||||
start = clock()
|
||||
for i in items:
|
||||
func(arg); func(arg); func(arg); func(arg); func(arg)
|
||||
stop = clock()
|
||||
else:
|
||||
start = clock()
|
||||
for i in items:
|
||||
func(); func(); func(); func(); func()
|
||||
stop = clock()
|
||||
print("%15s: %.2f us" % (msg, ((stop-start)*1e6/5/rep)))
|
||||
|
||||
def check_perf():
|
||||
# Construct 5 objects
|
||||
from ctypes import c_int
|
||||
|
||||
REP = 200000
|
||||
|
||||
run_test(REP, "int()", int)
|
||||
run_test(REP, "int(999)", int)
|
||||
run_test(REP, "c_int()", c_int)
|
||||
run_test(REP, "c_int(999)", c_int)
|
||||
run_test(REP, "c_int_S()", c_int_S)
|
||||
run_test(REP, "c_int_S(999)", c_int_S)
|
||||
|
||||
# Python 2.3 -OO, win2k, P4 700 MHz:
|
||||
#
|
||||
# int(): 0.87 us
|
||||
# int(999): 0.87 us
|
||||
# c_int(): 3.35 us
|
||||
# c_int(999): 3.34 us
|
||||
# c_int_S(): 3.23 us
|
||||
# c_int_S(999): 3.24 us
|
||||
|
||||
# Python 2.2 -OO, win2k, P4 700 MHz:
|
||||
#
|
||||
# int(): 0.89 us
|
||||
# int(999): 0.89 us
|
||||
# c_int(): 9.99 us
|
||||
# c_int(999): 10.02 us
|
||||
# c_int_S(): 9.87 us
|
||||
# c_int_S(999): 9.85 us
|
||||
|
||||
if __name__ == '__main__':
|
||||
## check_perf()
|
||||
unittest.main()
|
||||
|
||||
2527
Lib/datetime.py
vendored
2527
Lib/datetime.py
vendored
File diff suppressed because it is too large
Load Diff
4
Lib/encodings/__init__.py
vendored
4
Lib/encodings/__init__.py
vendored
@@ -156,6 +156,10 @@ def search_function(encoding):
|
||||
codecs.register(search_function)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# bpo-671666, bpo-46668: If Python does not implement a codec for current
|
||||
# Windows ANSI code page, use the "mbcs" codec instead:
|
||||
# WideCharToMultiByte() and MultiByteToWideChar() functions with CP_ACP.
|
||||
# Python does not support custom code pages.
|
||||
def _alias_mbcs(encoding):
|
||||
try:
|
||||
import _winapi
|
||||
|
||||
43
Lib/encodings/cp65001.py
vendored
43
Lib/encodings/cp65001.py
vendored
@@ -1,43 +0,0 @@
|
||||
"""
|
||||
Code page 65001: Windows UTF-8 (CP_UTF8).
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import functools
|
||||
|
||||
if not hasattr(codecs, 'code_page_encode'):
|
||||
raise LookupError("cp65001 encoding is only available on Windows")
|
||||
|
||||
### Codec APIs
|
||||
|
||||
encode = functools.partial(codecs.code_page_encode, 65001)
|
||||
_decode = functools.partial(codecs.code_page_decode, 65001)
|
||||
|
||||
def decode(input, errors='strict'):
|
||||
return codecs.code_page_decode(65001, input, errors, True)
|
||||
|
||||
class IncrementalEncoder(codecs.IncrementalEncoder):
|
||||
def encode(self, input, final=False):
|
||||
return encode(input, self.errors)[0]
|
||||
|
||||
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
|
||||
_buffer_decode = _decode
|
||||
|
||||
class StreamWriter(codecs.StreamWriter):
|
||||
encode = encode
|
||||
|
||||
class StreamReader(codecs.StreamReader):
|
||||
decode = _decode
|
||||
|
||||
### encodings module API
|
||||
|
||||
def getregentry():
|
||||
return codecs.CodecInfo(
|
||||
name='cp65001',
|
||||
encode=encode,
|
||||
decode=decode,
|
||||
incrementalencoder=IncrementalEncoder,
|
||||
incrementaldecoder=IncrementalDecoder,
|
||||
streamreader=StreamReader,
|
||||
streamwriter=StreamWriter,
|
||||
)
|
||||
42
Lib/encodings/idna.py
vendored
42
Lib/encodings/idna.py
vendored
@@ -39,23 +39,21 @@ def nameprep(label):
|
||||
|
||||
# Check bidi
|
||||
RandAL = [stringprep.in_table_d1(x) for x in label]
|
||||
for c in RandAL:
|
||||
if c:
|
||||
# There is a RandAL char in the string. Must perform further
|
||||
# tests:
|
||||
# 1) The characters in section 5.8 MUST be prohibited.
|
||||
# This is table C.8, which was already checked
|
||||
# 2) If a string contains any RandALCat character, the string
|
||||
# MUST NOT contain any LCat character.
|
||||
if any(stringprep.in_table_d2(x) for x in label):
|
||||
raise UnicodeError("Violation of BIDI requirement 2")
|
||||
|
||||
# 3) If a string contains any RandALCat character, a
|
||||
# RandALCat character MUST be the first character of the
|
||||
# string, and a RandALCat character MUST be the last
|
||||
# character of the string.
|
||||
if not RandAL[0] or not RandAL[-1]:
|
||||
raise UnicodeError("Violation of BIDI requirement 3")
|
||||
if any(RandAL):
|
||||
# There is a RandAL char in the string. Must perform further
|
||||
# tests:
|
||||
# 1) The characters in section 5.8 MUST be prohibited.
|
||||
# This is table C.8, which was already checked
|
||||
# 2) If a string contains any RandALCat character, the string
|
||||
# MUST NOT contain any LCat character.
|
||||
if any(stringprep.in_table_d2(x) for x in label):
|
||||
raise UnicodeError("Violation of BIDI requirement 2")
|
||||
# 3) If a string contains any RandALCat character, a
|
||||
# RandALCat character MUST be the first character of the
|
||||
# string, and a RandALCat character MUST be the last
|
||||
# character of the string.
|
||||
if not RandAL[0] or not RandAL[-1]:
|
||||
raise UnicodeError("Violation of BIDI requirement 3")
|
||||
|
||||
return label
|
||||
|
||||
@@ -103,6 +101,16 @@ def ToASCII(label):
|
||||
raise UnicodeError("label empty or too long")
|
||||
|
||||
def ToUnicode(label):
|
||||
if len(label) > 1024:
|
||||
# Protection from https://github.com/python/cpython/issues/98433.
|
||||
# https://datatracker.ietf.org/doc/html/rfc5894#section-6
|
||||
# doesn't specify a label size limit prior to NAMEPREP. But having
|
||||
# one makes practical sense.
|
||||
# This leaves ample room for nameprep() to remove Nothing characters
|
||||
# per https://www.rfc-editor.org/rfc/rfc3454#section-3.1 while still
|
||||
# preventing us from wasting time decoding a big thing that'll just
|
||||
# hit the actual <= 63 length limit in Step 6.
|
||||
raise UnicodeError("label way too long")
|
||||
# Step 1: Check for ASCII
|
||||
if isinstance(label, bytes):
|
||||
pure_ascii = True
|
||||
|
||||
307
Lib/encodings/mac_centeuro.py
vendored
307
Lib/encodings/mac_centeuro.py
vendored
@@ -1,307 +0,0 @@
|
||||
""" Python Character Mapping Codec mac_centeuro generated from 'MAPPINGS/VENDORS/APPLE/CENTEURO.TXT' with gencodec.py.
|
||||
|
||||
"""#"
|
||||
|
||||
import codecs
|
||||
|
||||
### Codec APIs
|
||||
|
||||
class Codec(codecs.Codec):
|
||||
|
||||
def encode(self,input,errors='strict'):
|
||||
return codecs.charmap_encode(input,errors,encoding_table)
|
||||
|
||||
def decode(self,input,errors='strict'):
|
||||
return codecs.charmap_decode(input,errors,decoding_table)
|
||||
|
||||
class IncrementalEncoder(codecs.IncrementalEncoder):
|
||||
def encode(self, input, final=False):
|
||||
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
|
||||
|
||||
class IncrementalDecoder(codecs.IncrementalDecoder):
|
||||
def decode(self, input, final=False):
|
||||
return codecs.charmap_decode(input,self.errors,decoding_table)[0]
|
||||
|
||||
class StreamWriter(Codec,codecs.StreamWriter):
|
||||
pass
|
||||
|
||||
class StreamReader(Codec,codecs.StreamReader):
|
||||
pass
|
||||
|
||||
### encodings module API
|
||||
|
||||
def getregentry():
|
||||
return codecs.CodecInfo(
|
||||
name='mac-centeuro',
|
||||
encode=Codec().encode,
|
||||
decode=Codec().decode,
|
||||
incrementalencoder=IncrementalEncoder,
|
||||
incrementaldecoder=IncrementalDecoder,
|
||||
streamreader=StreamReader,
|
||||
streamwriter=StreamWriter,
|
||||
)
|
||||
|
||||
|
||||
### Decoding Table
|
||||
|
||||
decoding_table = (
|
||||
'\x00' # 0x00 -> CONTROL CHARACTER
|
||||
'\x01' # 0x01 -> CONTROL CHARACTER
|
||||
'\x02' # 0x02 -> CONTROL CHARACTER
|
||||
'\x03' # 0x03 -> CONTROL CHARACTER
|
||||
'\x04' # 0x04 -> CONTROL CHARACTER
|
||||
'\x05' # 0x05 -> CONTROL CHARACTER
|
||||
'\x06' # 0x06 -> CONTROL CHARACTER
|
||||
'\x07' # 0x07 -> CONTROL CHARACTER
|
||||
'\x08' # 0x08 -> CONTROL CHARACTER
|
||||
'\t' # 0x09 -> CONTROL CHARACTER
|
||||
'\n' # 0x0A -> CONTROL CHARACTER
|
||||
'\x0b' # 0x0B -> CONTROL CHARACTER
|
||||
'\x0c' # 0x0C -> CONTROL CHARACTER
|
||||
'\r' # 0x0D -> CONTROL CHARACTER
|
||||
'\x0e' # 0x0E -> CONTROL CHARACTER
|
||||
'\x0f' # 0x0F -> CONTROL CHARACTER
|
||||
'\x10' # 0x10 -> CONTROL CHARACTER
|
||||
'\x11' # 0x11 -> CONTROL CHARACTER
|
||||
'\x12' # 0x12 -> CONTROL CHARACTER
|
||||
'\x13' # 0x13 -> CONTROL CHARACTER
|
||||
'\x14' # 0x14 -> CONTROL CHARACTER
|
||||
'\x15' # 0x15 -> CONTROL CHARACTER
|
||||
'\x16' # 0x16 -> CONTROL CHARACTER
|
||||
'\x17' # 0x17 -> CONTROL CHARACTER
|
||||
'\x18' # 0x18 -> CONTROL CHARACTER
|
||||
'\x19' # 0x19 -> CONTROL CHARACTER
|
||||
'\x1a' # 0x1A -> CONTROL CHARACTER
|
||||
'\x1b' # 0x1B -> CONTROL CHARACTER
|
||||
'\x1c' # 0x1C -> CONTROL CHARACTER
|
||||
'\x1d' # 0x1D -> CONTROL CHARACTER
|
||||
'\x1e' # 0x1E -> CONTROL CHARACTER
|
||||
'\x1f' # 0x1F -> CONTROL CHARACTER
|
||||
' ' # 0x20 -> SPACE
|
||||
'!' # 0x21 -> EXCLAMATION MARK
|
||||
'"' # 0x22 -> QUOTATION MARK
|
||||
'#' # 0x23 -> NUMBER SIGN
|
||||
'$' # 0x24 -> DOLLAR SIGN
|
||||
'%' # 0x25 -> PERCENT SIGN
|
||||
'&' # 0x26 -> AMPERSAND
|
||||
"'" # 0x27 -> APOSTROPHE
|
||||
'(' # 0x28 -> LEFT PARENTHESIS
|
||||
')' # 0x29 -> RIGHT PARENTHESIS
|
||||
'*' # 0x2A -> ASTERISK
|
||||
'+' # 0x2B -> PLUS SIGN
|
||||
',' # 0x2C -> COMMA
|
||||
'-' # 0x2D -> HYPHEN-MINUS
|
||||
'.' # 0x2E -> FULL STOP
|
||||
'/' # 0x2F -> SOLIDUS
|
||||
'0' # 0x30 -> DIGIT ZERO
|
||||
'1' # 0x31 -> DIGIT ONE
|
||||
'2' # 0x32 -> DIGIT TWO
|
||||
'3' # 0x33 -> DIGIT THREE
|
||||
'4' # 0x34 -> DIGIT FOUR
|
||||
'5' # 0x35 -> DIGIT FIVE
|
||||
'6' # 0x36 -> DIGIT SIX
|
||||
'7' # 0x37 -> DIGIT SEVEN
|
||||
'8' # 0x38 -> DIGIT EIGHT
|
||||
'9' # 0x39 -> DIGIT NINE
|
||||
':' # 0x3A -> COLON
|
||||
';' # 0x3B -> SEMICOLON
|
||||
'<' # 0x3C -> LESS-THAN SIGN
|
||||
'=' # 0x3D -> EQUALS SIGN
|
||||
'>' # 0x3E -> GREATER-THAN SIGN
|
||||
'?' # 0x3F -> QUESTION MARK
|
||||
'@' # 0x40 -> COMMERCIAL AT
|
||||
'A' # 0x41 -> LATIN CAPITAL LETTER A
|
||||
'B' # 0x42 -> LATIN CAPITAL LETTER B
|
||||
'C' # 0x43 -> LATIN CAPITAL LETTER C
|
||||
'D' # 0x44 -> LATIN CAPITAL LETTER D
|
||||
'E' # 0x45 -> LATIN CAPITAL LETTER E
|
||||
'F' # 0x46 -> LATIN CAPITAL LETTER F
|
||||
'G' # 0x47 -> LATIN CAPITAL LETTER G
|
||||
'H' # 0x48 -> LATIN CAPITAL LETTER H
|
||||
'I' # 0x49 -> LATIN CAPITAL LETTER I
|
||||
'J' # 0x4A -> LATIN CAPITAL LETTER J
|
||||
'K' # 0x4B -> LATIN CAPITAL LETTER K
|
||||
'L' # 0x4C -> LATIN CAPITAL LETTER L
|
||||
'M' # 0x4D -> LATIN CAPITAL LETTER M
|
||||
'N' # 0x4E -> LATIN CAPITAL LETTER N
|
||||
'O' # 0x4F -> LATIN CAPITAL LETTER O
|
||||
'P' # 0x50 -> LATIN CAPITAL LETTER P
|
||||
'Q' # 0x51 -> LATIN CAPITAL LETTER Q
|
||||
'R' # 0x52 -> LATIN CAPITAL LETTER R
|
||||
'S' # 0x53 -> LATIN CAPITAL LETTER S
|
||||
'T' # 0x54 -> LATIN CAPITAL LETTER T
|
||||
'U' # 0x55 -> LATIN CAPITAL LETTER U
|
||||
'V' # 0x56 -> LATIN CAPITAL LETTER V
|
||||
'W' # 0x57 -> LATIN CAPITAL LETTER W
|
||||
'X' # 0x58 -> LATIN CAPITAL LETTER X
|
||||
'Y' # 0x59 -> LATIN CAPITAL LETTER Y
|
||||
'Z' # 0x5A -> LATIN CAPITAL LETTER Z
|
||||
'[' # 0x5B -> LEFT SQUARE BRACKET
|
||||
'\\' # 0x5C -> REVERSE SOLIDUS
|
||||
']' # 0x5D -> RIGHT SQUARE BRACKET
|
||||
'^' # 0x5E -> CIRCUMFLEX ACCENT
|
||||
'_' # 0x5F -> LOW LINE
|
||||
'`' # 0x60 -> GRAVE ACCENT
|
||||
'a' # 0x61 -> LATIN SMALL LETTER A
|
||||
'b' # 0x62 -> LATIN SMALL LETTER B
|
||||
'c' # 0x63 -> LATIN SMALL LETTER C
|
||||
'd' # 0x64 -> LATIN SMALL LETTER D
|
||||
'e' # 0x65 -> LATIN SMALL LETTER E
|
||||
'f' # 0x66 -> LATIN SMALL LETTER F
|
||||
'g' # 0x67 -> LATIN SMALL LETTER G
|
||||
'h' # 0x68 -> LATIN SMALL LETTER H
|
||||
'i' # 0x69 -> LATIN SMALL LETTER I
|
||||
'j' # 0x6A -> LATIN SMALL LETTER J
|
||||
'k' # 0x6B -> LATIN SMALL LETTER K
|
||||
'l' # 0x6C -> LATIN SMALL LETTER L
|
||||
'm' # 0x6D -> LATIN SMALL LETTER M
|
||||
'n' # 0x6E -> LATIN SMALL LETTER N
|
||||
'o' # 0x6F -> LATIN SMALL LETTER O
|
||||
'p' # 0x70 -> LATIN SMALL LETTER P
|
||||
'q' # 0x71 -> LATIN SMALL LETTER Q
|
||||
'r' # 0x72 -> LATIN SMALL LETTER R
|
||||
's' # 0x73 -> LATIN SMALL LETTER S
|
||||
't' # 0x74 -> LATIN SMALL LETTER T
|
||||
'u' # 0x75 -> LATIN SMALL LETTER U
|
||||
'v' # 0x76 -> LATIN SMALL LETTER V
|
||||
'w' # 0x77 -> LATIN SMALL LETTER W
|
||||
'x' # 0x78 -> LATIN SMALL LETTER X
|
||||
'y' # 0x79 -> LATIN SMALL LETTER Y
|
||||
'z' # 0x7A -> LATIN SMALL LETTER Z
|
||||
'{' # 0x7B -> LEFT CURLY BRACKET
|
||||
'|' # 0x7C -> VERTICAL LINE
|
||||
'}' # 0x7D -> RIGHT CURLY BRACKET
|
||||
'~' # 0x7E -> TILDE
|
||||
'\x7f' # 0x7F -> CONTROL CHARACTER
|
||||
'\xc4' # 0x80 -> LATIN CAPITAL LETTER A WITH DIAERESIS
|
||||
'\u0100' # 0x81 -> LATIN CAPITAL LETTER A WITH MACRON
|
||||
'\u0101' # 0x82 -> LATIN SMALL LETTER A WITH MACRON
|
||||
'\xc9' # 0x83 -> LATIN CAPITAL LETTER E WITH ACUTE
|
||||
'\u0104' # 0x84 -> LATIN CAPITAL LETTER A WITH OGONEK
|
||||
'\xd6' # 0x85 -> LATIN CAPITAL LETTER O WITH DIAERESIS
|
||||
'\xdc' # 0x86 -> LATIN CAPITAL LETTER U WITH DIAERESIS
|
||||
'\xe1' # 0x87 -> LATIN SMALL LETTER A WITH ACUTE
|
||||
'\u0105' # 0x88 -> LATIN SMALL LETTER A WITH OGONEK
|
||||
'\u010c' # 0x89 -> LATIN CAPITAL LETTER C WITH CARON
|
||||
'\xe4' # 0x8A -> LATIN SMALL LETTER A WITH DIAERESIS
|
||||
'\u010d' # 0x8B -> LATIN SMALL LETTER C WITH CARON
|
||||
'\u0106' # 0x8C -> LATIN CAPITAL LETTER C WITH ACUTE
|
||||
'\u0107' # 0x8D -> LATIN SMALL LETTER C WITH ACUTE
|
||||
'\xe9' # 0x8E -> LATIN SMALL LETTER E WITH ACUTE
|
||||
'\u0179' # 0x8F -> LATIN CAPITAL LETTER Z WITH ACUTE
|
||||
'\u017a' # 0x90 -> LATIN SMALL LETTER Z WITH ACUTE
|
||||
'\u010e' # 0x91 -> LATIN CAPITAL LETTER D WITH CARON
|
||||
'\xed' # 0x92 -> LATIN SMALL LETTER I WITH ACUTE
|
||||
'\u010f' # 0x93 -> LATIN SMALL LETTER D WITH CARON
|
||||
'\u0112' # 0x94 -> LATIN CAPITAL LETTER E WITH MACRON
|
||||
'\u0113' # 0x95 -> LATIN SMALL LETTER E WITH MACRON
|
||||
'\u0116' # 0x96 -> LATIN CAPITAL LETTER E WITH DOT ABOVE
|
||||
'\xf3' # 0x97 -> LATIN SMALL LETTER O WITH ACUTE
|
||||
'\u0117' # 0x98 -> LATIN SMALL LETTER E WITH DOT ABOVE
|
||||
'\xf4' # 0x99 -> LATIN SMALL LETTER O WITH CIRCUMFLEX
|
||||
'\xf6' # 0x9A -> LATIN SMALL LETTER O WITH DIAERESIS
|
||||
'\xf5' # 0x9B -> LATIN SMALL LETTER O WITH TILDE
|
||||
'\xfa' # 0x9C -> LATIN SMALL LETTER U WITH ACUTE
|
||||
'\u011a' # 0x9D -> LATIN CAPITAL LETTER E WITH CARON
|
||||
'\u011b' # 0x9E -> LATIN SMALL LETTER E WITH CARON
|
||||
'\xfc' # 0x9F -> LATIN SMALL LETTER U WITH DIAERESIS
|
||||
'\u2020' # 0xA0 -> DAGGER
|
||||
'\xb0' # 0xA1 -> DEGREE SIGN
|
||||
'\u0118' # 0xA2 -> LATIN CAPITAL LETTER E WITH OGONEK
|
||||
'\xa3' # 0xA3 -> POUND SIGN
|
||||
'\xa7' # 0xA4 -> SECTION SIGN
|
||||
'\u2022' # 0xA5 -> BULLET
|
||||
'\xb6' # 0xA6 -> PILCROW SIGN
|
||||
'\xdf' # 0xA7 -> LATIN SMALL LETTER SHARP S
|
||||
'\xae' # 0xA8 -> REGISTERED SIGN
|
||||
'\xa9' # 0xA9 -> COPYRIGHT SIGN
|
||||
'\u2122' # 0xAA -> TRADE MARK SIGN
|
||||
'\u0119' # 0xAB -> LATIN SMALL LETTER E WITH OGONEK
|
||||
'\xa8' # 0xAC -> DIAERESIS
|
||||
'\u2260' # 0xAD -> NOT EQUAL TO
|
||||
'\u0123' # 0xAE -> LATIN SMALL LETTER G WITH CEDILLA
|
||||
'\u012e' # 0xAF -> LATIN CAPITAL LETTER I WITH OGONEK
|
||||
'\u012f' # 0xB0 -> LATIN SMALL LETTER I WITH OGONEK
|
||||
'\u012a' # 0xB1 -> LATIN CAPITAL LETTER I WITH MACRON
|
||||
'\u2264' # 0xB2 -> LESS-THAN OR EQUAL TO
|
||||
'\u2265' # 0xB3 -> GREATER-THAN OR EQUAL TO
|
||||
'\u012b' # 0xB4 -> LATIN SMALL LETTER I WITH MACRON
|
||||
'\u0136' # 0xB5 -> LATIN CAPITAL LETTER K WITH CEDILLA
|
||||
'\u2202' # 0xB6 -> PARTIAL DIFFERENTIAL
|
||||
'\u2211' # 0xB7 -> N-ARY SUMMATION
|
||||
'\u0142' # 0xB8 -> LATIN SMALL LETTER L WITH STROKE
|
||||
'\u013b' # 0xB9 -> LATIN CAPITAL LETTER L WITH CEDILLA
|
||||
'\u013c' # 0xBA -> LATIN SMALL LETTER L WITH CEDILLA
|
||||
'\u013d' # 0xBB -> LATIN CAPITAL LETTER L WITH CARON
|
||||
'\u013e' # 0xBC -> LATIN SMALL LETTER L WITH CARON
|
||||
'\u0139' # 0xBD -> LATIN CAPITAL LETTER L WITH ACUTE
|
||||
'\u013a' # 0xBE -> LATIN SMALL LETTER L WITH ACUTE
|
||||
'\u0145' # 0xBF -> LATIN CAPITAL LETTER N WITH CEDILLA
|
||||
'\u0146' # 0xC0 -> LATIN SMALL LETTER N WITH CEDILLA
|
||||
'\u0143' # 0xC1 -> LATIN CAPITAL LETTER N WITH ACUTE
|
||||
'\xac' # 0xC2 -> NOT SIGN
|
||||
'\u221a' # 0xC3 -> SQUARE ROOT
|
||||
'\u0144' # 0xC4 -> LATIN SMALL LETTER N WITH ACUTE
|
||||
'\u0147' # 0xC5 -> LATIN CAPITAL LETTER N WITH CARON
|
||||
'\u2206' # 0xC6 -> INCREMENT
|
||||
'\xab' # 0xC7 -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
|
||||
'\xbb' # 0xC8 -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
|
||||
'\u2026' # 0xC9 -> HORIZONTAL ELLIPSIS
|
||||
'\xa0' # 0xCA -> NO-BREAK SPACE
|
||||
'\u0148' # 0xCB -> LATIN SMALL LETTER N WITH CARON
|
||||
'\u0150' # 0xCC -> LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
|
||||
'\xd5' # 0xCD -> LATIN CAPITAL LETTER O WITH TILDE
|
||||
'\u0151' # 0xCE -> LATIN SMALL LETTER O WITH DOUBLE ACUTE
|
||||
'\u014c' # 0xCF -> LATIN CAPITAL LETTER O WITH MACRON
|
||||
'\u2013' # 0xD0 -> EN DASH
|
||||
'\u2014' # 0xD1 -> EM DASH
|
||||
'\u201c' # 0xD2 -> LEFT DOUBLE QUOTATION MARK
|
||||
'\u201d' # 0xD3 -> RIGHT DOUBLE QUOTATION MARK
|
||||
'\u2018' # 0xD4 -> LEFT SINGLE QUOTATION MARK
|
||||
'\u2019' # 0xD5 -> RIGHT SINGLE QUOTATION MARK
|
||||
'\xf7' # 0xD6 -> DIVISION SIGN
|
||||
'\u25ca' # 0xD7 -> LOZENGE
|
||||
'\u014d' # 0xD8 -> LATIN SMALL LETTER O WITH MACRON
|
||||
'\u0154' # 0xD9 -> LATIN CAPITAL LETTER R WITH ACUTE
|
||||
'\u0155' # 0xDA -> LATIN SMALL LETTER R WITH ACUTE
|
||||
'\u0158' # 0xDB -> LATIN CAPITAL LETTER R WITH CARON
|
||||
'\u2039' # 0xDC -> SINGLE LEFT-POINTING ANGLE QUOTATION MARK
|
||||
'\u203a' # 0xDD -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
|
||||
'\u0159' # 0xDE -> LATIN SMALL LETTER R WITH CARON
|
||||
'\u0156' # 0xDF -> LATIN CAPITAL LETTER R WITH CEDILLA
|
||||
'\u0157' # 0xE0 -> LATIN SMALL LETTER R WITH CEDILLA
|
||||
'\u0160' # 0xE1 -> LATIN CAPITAL LETTER S WITH CARON
|
||||
'\u201a' # 0xE2 -> SINGLE LOW-9 QUOTATION MARK
|
||||
'\u201e' # 0xE3 -> DOUBLE LOW-9 QUOTATION MARK
|
||||
'\u0161' # 0xE4 -> LATIN SMALL LETTER S WITH CARON
|
||||
'\u015a' # 0xE5 -> LATIN CAPITAL LETTER S WITH ACUTE
|
||||
'\u015b' # 0xE6 -> LATIN SMALL LETTER S WITH ACUTE
|
||||
'\xc1' # 0xE7 -> LATIN CAPITAL LETTER A WITH ACUTE
|
||||
'\u0164' # 0xE8 -> LATIN CAPITAL LETTER T WITH CARON
|
||||
'\u0165' # 0xE9 -> LATIN SMALL LETTER T WITH CARON
|
||||
'\xcd' # 0xEA -> LATIN CAPITAL LETTER I WITH ACUTE
|
||||
'\u017d' # 0xEB -> LATIN CAPITAL LETTER Z WITH CARON
|
||||
'\u017e' # 0xEC -> LATIN SMALL LETTER Z WITH CARON
|
||||
'\u016a' # 0xED -> LATIN CAPITAL LETTER U WITH MACRON
|
||||
'\xd3' # 0xEE -> LATIN CAPITAL LETTER O WITH ACUTE
|
||||
'\xd4' # 0xEF -> LATIN CAPITAL LETTER O WITH CIRCUMFLEX
|
||||
'\u016b' # 0xF0 -> LATIN SMALL LETTER U WITH MACRON
|
||||
'\u016e' # 0xF1 -> LATIN CAPITAL LETTER U WITH RING ABOVE
|
||||
'\xda' # 0xF2 -> LATIN CAPITAL LETTER U WITH ACUTE
|
||||
'\u016f' # 0xF3 -> LATIN SMALL LETTER U WITH RING ABOVE
|
||||
'\u0170' # 0xF4 -> LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
|
||||
'\u0171' # 0xF5 -> LATIN SMALL LETTER U WITH DOUBLE ACUTE
|
||||
'\u0172' # 0xF6 -> LATIN CAPITAL LETTER U WITH OGONEK
|
||||
'\u0173' # 0xF7 -> LATIN SMALL LETTER U WITH OGONEK
|
||||
'\xdd' # 0xF8 -> LATIN CAPITAL LETTER Y WITH ACUTE
|
||||
'\xfd' # 0xF9 -> LATIN SMALL LETTER Y WITH ACUTE
|
||||
'\u0137' # 0xFA -> LATIN SMALL LETTER K WITH CEDILLA
|
||||
'\u017b' # 0xFB -> LATIN CAPITAL LETTER Z WITH DOT ABOVE
|
||||
'\u0141' # 0xFC -> LATIN CAPITAL LETTER L WITH STROKE
|
||||
'\u017c' # 0xFD -> LATIN SMALL LETTER Z WITH DOT ABOVE
|
||||
'\u0122' # 0xFE -> LATIN CAPITAL LETTER G WITH CEDILLA
|
||||
'\u02c7' # 0xFF -> CARON
|
||||
)
|
||||
|
||||
### Encoding table
|
||||
encoding_table=codecs.charmap_build(decoding_table)
|
||||
45
Lib/encodings/unicode_internal.py
vendored
45
Lib/encodings/unicode_internal.py
vendored
@@ -1,45 +0,0 @@
|
||||
""" Python 'unicode-internal' Codec
|
||||
|
||||
|
||||
Written by Marc-Andre Lemburg (mal@lemburg.com).
|
||||
|
||||
(c) Copyright CNRI, All Rights Reserved. NO WARRANTY.
|
||||
|
||||
"""
|
||||
import codecs
|
||||
|
||||
### Codec APIs
|
||||
|
||||
class Codec(codecs.Codec):
|
||||
|
||||
# Note: Binding these as C functions will result in the class not
|
||||
# converting them to methods. This is intended.
|
||||
encode = codecs.unicode_internal_encode
|
||||
decode = codecs.unicode_internal_decode
|
||||
|
||||
class IncrementalEncoder(codecs.IncrementalEncoder):
|
||||
def encode(self, input, final=False):
|
||||
return codecs.unicode_internal_encode(input, self.errors)[0]
|
||||
|
||||
class IncrementalDecoder(codecs.IncrementalDecoder):
|
||||
def decode(self, input, final=False):
|
||||
return codecs.unicode_internal_decode(input, self.errors)[0]
|
||||
|
||||
class StreamWriter(Codec,codecs.StreamWriter):
|
||||
pass
|
||||
|
||||
class StreamReader(Codec,codecs.StreamReader):
|
||||
pass
|
||||
|
||||
### encodings module API
|
||||
|
||||
def getregentry():
|
||||
return codecs.CodecInfo(
|
||||
name='unicode-internal',
|
||||
encode=Codec.encode,
|
||||
decode=Codec.decode,
|
||||
incrementalencoder=IncrementalEncoder,
|
||||
incrementaldecoder=IncrementalDecoder,
|
||||
streamwriter=StreamWriter,
|
||||
streamreader=StreamReader,
|
||||
)
|
||||
18
Lib/ensurepip/__init__.py
vendored
18
Lib/ensurepip/__init__.py
vendored
@@ -9,11 +9,9 @@ from importlib import resources
|
||||
|
||||
|
||||
__all__ = ["version", "bootstrap"]
|
||||
_PACKAGE_NAMES = ('setuptools', 'pip')
|
||||
_SETUPTOOLS_VERSION = "65.5.0"
|
||||
_PIP_VERSION = "22.3.1"
|
||||
_PACKAGE_NAMES = ('pip',)
|
||||
_PIP_VERSION = "23.2.1"
|
||||
_PROJECTS = [
|
||||
("setuptools", _SETUPTOOLS_VERSION, "py3"),
|
||||
("pip", _PIP_VERSION, "py3"),
|
||||
]
|
||||
|
||||
@@ -153,17 +151,17 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
|
||||
|
||||
_disable_pip_configuration_settings()
|
||||
|
||||
# By default, installing pip and setuptools installs all of the
|
||||
# By default, installing pip installs all of the
|
||||
# following scripts (X.Y == running Python version):
|
||||
#
|
||||
# pip, pipX, pipX.Y, easy_install, easy_install-X.Y
|
||||
# pip, pipX, pipX.Y
|
||||
#
|
||||
# pip 1.5+ allows ensurepip to request that some of those be left out
|
||||
if altinstall:
|
||||
# omit pip, pipX and easy_install
|
||||
# omit pip, pipX
|
||||
os.environ["ENSUREPIP_OPTIONS"] = "altinstall"
|
||||
elif not default_pip:
|
||||
# omit pip and easy_install
|
||||
# omit pip
|
||||
os.environ["ENSUREPIP_OPTIONS"] = "install"
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
@@ -271,14 +269,14 @@ def _main(argv=None):
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=("Make an alternate install, installing only the X.Y versioned "
|
||||
"scripts (Default: pipX, pipX.Y, easy_install-X.Y)."),
|
||||
"scripts (Default: pipX, pipX.Y)."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--default-pip",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=("Make a default pip install, installing the unqualified pip "
|
||||
"and easy_install in addition to the versioned scripts."),
|
||||
"in addition to the versioned scripts."),
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
2040
Lib/enum.py
vendored
2040
Lib/enum.py
vendored
File diff suppressed because it is too large
Load Diff
13
Lib/filecmp.py
vendored
13
Lib/filecmp.py
vendored
@@ -10,10 +10,7 @@ Functions:
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
import os
|
||||
except ImportError:
|
||||
import _dummy_os as os
|
||||
import os
|
||||
import stat
|
||||
from itertools import filterfalse
|
||||
from types import GenericAlias
|
||||
@@ -160,17 +157,17 @@ class dircmp:
|
||||
a_path = os.path.join(self.left, x)
|
||||
b_path = os.path.join(self.right, x)
|
||||
|
||||
ok = 1
|
||||
ok = True
|
||||
try:
|
||||
a_stat = os.stat(a_path)
|
||||
except OSError:
|
||||
# print('Can\'t stat', a_path, ':', why.args[1])
|
||||
ok = 0
|
||||
ok = False
|
||||
try:
|
||||
b_stat = os.stat(b_path)
|
||||
except OSError:
|
||||
# print('Can\'t stat', b_path, ':', why.args[1])
|
||||
ok = 0
|
||||
ok = False
|
||||
|
||||
if ok:
|
||||
a_type = stat.S_IFMT(a_stat.st_mode)
|
||||
@@ -245,7 +242,7 @@ class dircmp:
|
||||
|
||||
methodmap = dict(subdirs=phase4,
|
||||
same_files=phase3, diff_files=phase3, funny_files=phase3,
|
||||
common_dirs = phase2, common_files=phase2, common_funny=phase2,
|
||||
common_dirs=phase2, common_files=phase2, common_funny=phase2,
|
||||
common=phase1, left_only=phase1, right_only=phase1,
|
||||
left_list=phase0, right_list=phase0)
|
||||
|
||||
|
||||
26
Lib/fileinput.py
vendored
26
Lib/fileinput.py
vendored
@@ -217,15 +217,10 @@ class FileInput:
|
||||
EncodingWarning, 2)
|
||||
|
||||
# restrict mode argument to reading modes
|
||||
if mode not in ('r', 'rU', 'U', 'rb'):
|
||||
raise ValueError("FileInput opening mode must be one of "
|
||||
"'r', 'rU', 'U' and 'rb'")
|
||||
if 'U' in mode:
|
||||
import warnings
|
||||
warnings.warn("'U' mode is deprecated",
|
||||
DeprecationWarning, 2)
|
||||
if mode not in ('r', 'rb'):
|
||||
raise ValueError("FileInput opening mode must be 'r' or 'rb'")
|
||||
self._mode = mode
|
||||
self._write_mode = mode.replace('r', 'w') if 'U' not in mode else 'w'
|
||||
self._write_mode = mode.replace('r', 'w')
|
||||
if openhook:
|
||||
if inplace:
|
||||
raise ValueError("FileInput cannot use an opening hook in inplace mode")
|
||||
@@ -262,21 +257,6 @@ class FileInput:
|
||||
self.nextfile()
|
||||
# repeat with next file
|
||||
|
||||
def __getitem__(self, i):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"Support for indexing FileInput objects is deprecated. "
|
||||
"Use iterator protocol instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
if i != self.lineno():
|
||||
raise RuntimeError("accessing lines out of order")
|
||||
try:
|
||||
return self.__next__()
|
||||
except StopIteration:
|
||||
raise IndexError("end of input reached")
|
||||
|
||||
def nextfile(self):
|
||||
savestdout = self._savestdout
|
||||
self._savestdout = None
|
||||
|
||||
544
Lib/fractions.py
vendored
544
Lib/fractions.py
vendored
@@ -1,40 +1,19 @@
|
||||
# Originally contributed by Sjoerd Mullender.
|
||||
# Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>.
|
||||
|
||||
"""Fraction, infinite-precision, real numbers."""
|
||||
"""Fraction, infinite-precision, rational numbers."""
|
||||
|
||||
from decimal import Decimal
|
||||
import functools
|
||||
import math
|
||||
import numbers
|
||||
import operator
|
||||
import re
|
||||
import sys
|
||||
|
||||
__all__ = ['Fraction', 'gcd']
|
||||
__all__ = ['Fraction']
|
||||
|
||||
|
||||
|
||||
def gcd(a, b):
|
||||
"""Calculate the Greatest Common Divisor of a and b.
|
||||
|
||||
Unless b==0, the result will have the same sign as b (so that when
|
||||
b is divided by it, the result comes out positive).
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn('fractions.gcd() is deprecated. Use math.gcd() instead.',
|
||||
DeprecationWarning, 2)
|
||||
if type(a) is int is type(b):
|
||||
if (b or a) < 0:
|
||||
return -math.gcd(a, b)
|
||||
return math.gcd(a, b)
|
||||
return _gcd(a, b)
|
||||
|
||||
def _gcd(a, b):
|
||||
# Supports non-integers for backward compatibility.
|
||||
while b:
|
||||
a, b = b, a%b
|
||||
return a
|
||||
|
||||
# Constants related to the hash implementation; hash(x) is based
|
||||
# on the reduction of x modulo the prime _PyHASH_MODULUS.
|
||||
_PyHASH_MODULUS = sys.hash_info.modulus
|
||||
@@ -42,21 +21,144 @@ _PyHASH_MODULUS = sys.hash_info.modulus
|
||||
# _PyHASH_MODULUS.
|
||||
_PyHASH_INF = sys.hash_info.inf
|
||||
|
||||
@functools.lru_cache(maxsize = 1 << 14)
|
||||
def _hash_algorithm(numerator, denominator):
|
||||
|
||||
# To make sure that the hash of a Fraction agrees with the hash
|
||||
# of a numerically equal integer, float or Decimal instance, we
|
||||
# follow the rules for numeric hashes outlined in the
|
||||
# documentation. (See library docs, 'Built-in Types').
|
||||
|
||||
try:
|
||||
dinv = pow(denominator, -1, _PyHASH_MODULUS)
|
||||
except ValueError:
|
||||
# ValueError means there is no modular inverse.
|
||||
hash_ = _PyHASH_INF
|
||||
else:
|
||||
# The general algorithm now specifies that the absolute value of
|
||||
# the hash is
|
||||
# (|N| * dinv) % P
|
||||
# where N is self._numerator and P is _PyHASH_MODULUS. That's
|
||||
# optimized here in two ways: first, for a non-negative int i,
|
||||
# hash(i) == i % P, but the int hash implementation doesn't need
|
||||
# to divide, and is faster than doing % P explicitly. So we do
|
||||
# hash(|N| * dinv)
|
||||
# instead. Second, N is unbounded, so its product with dinv may
|
||||
# be arbitrarily expensive to compute. The final answer is the
|
||||
# same if we use the bounded |N| % P instead, which can again
|
||||
# be done with an int hash() call. If 0 <= i < P, hash(i) == i,
|
||||
# so this nested hash() call wastes a bit of time making a
|
||||
# redundant copy when |N| < P, but can save an arbitrarily large
|
||||
# amount of computation for large |N|.
|
||||
hash_ = hash(hash(abs(numerator)) * dinv)
|
||||
result = hash_ if numerator >= 0 else -hash_
|
||||
return -2 if result == -1 else result
|
||||
|
||||
_RATIONAL_FORMAT = re.compile(r"""
|
||||
\A\s* # optional whitespace at the start, then
|
||||
(?P<sign>[-+]?) # an optional sign, then
|
||||
(?=\d|\.\d) # lookahead for digit or .digit
|
||||
(?P<num>\d*) # numerator (possibly empty)
|
||||
(?: # followed by
|
||||
(?:/(?P<denom>\d+))? # an optional denominator
|
||||
| # or
|
||||
(?:\.(?P<decimal>\d*))? # an optional fractional part
|
||||
(?:E(?P<exp>[-+]?\d+))? # and optional exponent
|
||||
\A\s* # optional whitespace at the start,
|
||||
(?P<sign>[-+]?) # an optional sign, then
|
||||
(?=\d|\.\d) # lookahead for digit or .digit
|
||||
(?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
|
||||
(?: # followed by
|
||||
(?:\s*/\s*(?P<denom>\d+(_\d+)*))? # an optional denominator
|
||||
| # or
|
||||
(?:\.(?P<decimal>\d*|\d+(_\d+)*))? # an optional fractional part
|
||||
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
|
||||
)
|
||||
\s*\Z # and optional whitespace to finish
|
||||
\s*\Z # and optional whitespace to finish
|
||||
""", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
|
||||
# Helpers for formatting
|
||||
|
||||
def _round_to_exponent(n, d, exponent, no_neg_zero=False):
|
||||
"""Round a rational number to the nearest multiple of a given power of 10.
|
||||
|
||||
Rounds the rational number n/d to the nearest integer multiple of
|
||||
10**exponent, rounding to the nearest even integer multiple in the case of
|
||||
a tie. Returns a pair (sign: bool, significand: int) representing the
|
||||
rounded value (-1)**sign * significand * 10**exponent.
|
||||
|
||||
If no_neg_zero is true, then the returned sign will always be False when
|
||||
the significand is zero. Otherwise, the sign reflects the sign of the
|
||||
input.
|
||||
|
||||
d must be positive, but n and d need not be relatively prime.
|
||||
"""
|
||||
if exponent >= 0:
|
||||
d *= 10**exponent
|
||||
else:
|
||||
n *= 10**-exponent
|
||||
|
||||
# The divmod quotient is correct for round-ties-towards-positive-infinity;
|
||||
# In the case of a tie, we zero out the least significant bit of q.
|
||||
q, r = divmod(n + (d >> 1), d)
|
||||
if r == 0 and d & 1 == 0:
|
||||
q &= -2
|
||||
|
||||
sign = q < 0 if no_neg_zero else n < 0
|
||||
return sign, abs(q)
|
||||
|
||||
|
||||
def _round_to_figures(n, d, figures):
|
||||
"""Round a rational number to a given number of significant figures.
|
||||
|
||||
Rounds the rational number n/d to the given number of significant figures
|
||||
using the round-ties-to-even rule, and returns a triple
|
||||
(sign: bool, significand: int, exponent: int) representing the rounded
|
||||
value (-1)**sign * significand * 10**exponent.
|
||||
|
||||
In the special case where n = 0, returns a significand of zero and
|
||||
an exponent of 1 - figures, for compatibility with formatting.
|
||||
Otherwise, the returned significand satisfies
|
||||
10**(figures - 1) <= significand < 10**figures.
|
||||
|
||||
d must be positive, but n and d need not be relatively prime.
|
||||
figures must be positive.
|
||||
"""
|
||||
# Special case for n == 0.
|
||||
if n == 0:
|
||||
return False, 0, 1 - figures
|
||||
|
||||
# Find integer m satisfying 10**(m - 1) <= abs(n)/d <= 10**m. (If abs(n)/d
|
||||
# is a power of 10, either of the two possible values for m is fine.)
|
||||
str_n, str_d = str(abs(n)), str(d)
|
||||
m = len(str_n) - len(str_d) + (str_d <= str_n)
|
||||
|
||||
# Round to a multiple of 10**(m - figures). The significand we get
|
||||
# satisfies 10**(figures - 1) <= significand <= 10**figures.
|
||||
exponent = m - figures
|
||||
sign, significand = _round_to_exponent(n, d, exponent)
|
||||
|
||||
# Adjust in the case where significand == 10**figures, to ensure that
|
||||
# 10**(figures - 1) <= significand < 10**figures.
|
||||
if len(str(significand)) == figures + 1:
|
||||
significand //= 10
|
||||
exponent += 1
|
||||
|
||||
return sign, significand, exponent
|
||||
|
||||
|
||||
# Pattern for matching float-style format specifications;
|
||||
# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
|
||||
_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
|
||||
(?:
|
||||
(?P<fill>.)?
|
||||
(?P<align>[<>=^])
|
||||
)?
|
||||
(?P<sign>[-+ ]?)
|
||||
(?P<no_neg_zero>z)?
|
||||
(?P<alt>\#)?
|
||||
# A '0' that's *not* followed by another digit is parsed as a minimum width
|
||||
# rather than a zeropad flag.
|
||||
(?P<zeropad>0(?=[0-9]))?
|
||||
(?P<minimumwidth>0|[1-9][0-9]*)?
|
||||
(?P<thousands_sep>[,_])?
|
||||
(?:\.(?P<precision>0|[1-9][0-9]*))?
|
||||
(?P<presentation_type>[eEfFgG%])
|
||||
""", re.DOTALL | re.VERBOSE).fullmatch
|
||||
|
||||
|
||||
class Fraction(numbers.Rational):
|
||||
"""This class implements rational numbers.
|
||||
|
||||
@@ -81,7 +183,7 @@ class Fraction(numbers.Rational):
|
||||
__slots__ = ('_numerator', '_denominator')
|
||||
|
||||
# We're immutable, so use __new__ not __init__
|
||||
def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
|
||||
def __new__(cls, numerator=0, denominator=None):
|
||||
"""Constructs a Rational.
|
||||
|
||||
Takes a string like '3/2' or '1.5', another Rational instance, a
|
||||
@@ -144,6 +246,7 @@ class Fraction(numbers.Rational):
|
||||
denominator = 1
|
||||
decimal = m.group('decimal')
|
||||
if decimal:
|
||||
decimal = decimal.replace('_', '')
|
||||
scale = 10**len(decimal)
|
||||
numerator = numerator * scale + int(decimal)
|
||||
denominator *= scale
|
||||
@@ -176,16 +279,11 @@ class Fraction(numbers.Rational):
|
||||
|
||||
if denominator == 0:
|
||||
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
|
||||
if _normalize:
|
||||
if type(numerator) is int is type(denominator):
|
||||
# *very* normal case
|
||||
g = math.gcd(numerator, denominator)
|
||||
if denominator < 0:
|
||||
g = -g
|
||||
else:
|
||||
g = _gcd(numerator, denominator)
|
||||
numerator //= g
|
||||
denominator //= g
|
||||
g = math.gcd(numerator, denominator)
|
||||
if denominator < 0:
|
||||
g = -g
|
||||
numerator //= g
|
||||
denominator //= g
|
||||
self._numerator = numerator
|
||||
self._denominator = denominator
|
||||
return self
|
||||
@@ -202,7 +300,7 @@ class Fraction(numbers.Rational):
|
||||
elif not isinstance(f, float):
|
||||
raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
|
||||
(cls.__name__, f, type(f).__name__))
|
||||
return cls(*f.as_integer_ratio())
|
||||
return cls._from_coprime_ints(*f.as_integer_ratio())
|
||||
|
||||
@classmethod
|
||||
def from_decimal(cls, dec):
|
||||
@@ -214,13 +312,28 @@ class Fraction(numbers.Rational):
|
||||
raise TypeError(
|
||||
"%s.from_decimal() only takes Decimals, not %r (%s)" %
|
||||
(cls.__name__, dec, type(dec).__name__))
|
||||
return cls(*dec.as_integer_ratio())
|
||||
return cls._from_coprime_ints(*dec.as_integer_ratio())
|
||||
|
||||
@classmethod
|
||||
def _from_coprime_ints(cls, numerator, denominator, /):
|
||||
"""Convert a pair of ints to a rational number, for internal use.
|
||||
|
||||
The ratio of integers should be in lowest terms and the denominator
|
||||
should be positive.
|
||||
"""
|
||||
obj = super(Fraction, cls).__new__(cls)
|
||||
obj._numerator = numerator
|
||||
obj._denominator = denominator
|
||||
return obj
|
||||
|
||||
def is_integer(self):
|
||||
"""Return True if the Fraction is an integer."""
|
||||
return self._denominator == 1
|
||||
|
||||
def as_integer_ratio(self):
|
||||
"""Return the integer ratio as a tuple.
|
||||
"""Return a pair of integers, whose ratio is equal to the original Fraction.
|
||||
|
||||
Return a tuple of two integers, whose ratio is equal to the
|
||||
Fraction and with a positive denominator.
|
||||
The ratio is in lowest terms and has a positive denominator.
|
||||
"""
|
||||
return (self._numerator, self._denominator)
|
||||
|
||||
@@ -270,14 +383,16 @@ class Fraction(numbers.Rational):
|
||||
break
|
||||
p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
|
||||
n, d = d, n-a*d
|
||||
|
||||
k = (max_denominator-q0)//q1
|
||||
bound1 = Fraction(p0+k*p1, q0+k*q1)
|
||||
bound2 = Fraction(p1, q1)
|
||||
if abs(bound2 - self) <= abs(bound1-self):
|
||||
return bound2
|
||||
|
||||
# Determine which of the candidates (p0+k*p1)/(q0+k*q1) and p1/q1 is
|
||||
# closer to self. The distance between them is 1/(q1*(q0+k*q1)), while
|
||||
# the distance from p1/q1 to self is d/(q1*self._denominator). So we
|
||||
# need to compare 2*(q0+k*q1) with self._denominator/d.
|
||||
if 2*d*(q0+k*q1) <= self._denominator:
|
||||
return Fraction._from_coprime_ints(p1, q1)
|
||||
else:
|
||||
return bound1
|
||||
return Fraction._from_coprime_ints(p0+k*p1, q0+k*q1)
|
||||
|
||||
@property
|
||||
def numerator(a):
|
||||
@@ -299,6 +414,122 @@ class Fraction(numbers.Rational):
|
||||
else:
|
||||
return '%s/%s' % (self._numerator, self._denominator)
|
||||
|
||||
def __format__(self, format_spec, /):
|
||||
"""Format this fraction according to the given format specification."""
|
||||
|
||||
# Backwards compatiblility with existing formatting.
|
||||
if not format_spec:
|
||||
return str(self)
|
||||
|
||||
# Validate and parse the format specifier.
|
||||
match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
|
||||
if match is None:
|
||||
raise ValueError(
|
||||
f"Invalid format specifier {format_spec!r} "
|
||||
f"for object of type {type(self).__name__!r}"
|
||||
)
|
||||
elif match["align"] is not None and match["zeropad"] is not None:
|
||||
# Avoid the temptation to guess.
|
||||
raise ValueError(
|
||||
f"Invalid format specifier {format_spec!r} "
|
||||
f"for object of type {type(self).__name__!r}; "
|
||||
"can't use explicit alignment when zero-padding"
|
||||
)
|
||||
fill = match["fill"] or " "
|
||||
align = match["align"] or ">"
|
||||
pos_sign = "" if match["sign"] == "-" else match["sign"]
|
||||
no_neg_zero = bool(match["no_neg_zero"])
|
||||
alternate_form = bool(match["alt"])
|
||||
zeropad = bool(match["zeropad"])
|
||||
minimumwidth = int(match["minimumwidth"] or "0")
|
||||
thousands_sep = match["thousands_sep"]
|
||||
precision = int(match["precision"] or "6")
|
||||
presentation_type = match["presentation_type"]
|
||||
trim_zeros = presentation_type in "gG" and not alternate_form
|
||||
trim_point = not alternate_form
|
||||
exponent_indicator = "E" if presentation_type in "EFG" else "e"
|
||||
|
||||
# Round to get the digits we need, figure out where to place the point,
|
||||
# and decide whether to use scientific notation. 'point_pos' is the
|
||||
# relative to the _end_ of the digit string: that is, it's the number
|
||||
# of digits that should follow the point.
|
||||
if presentation_type in "fF%":
|
||||
exponent = -precision
|
||||
if presentation_type == "%":
|
||||
exponent -= 2
|
||||
negative, significand = _round_to_exponent(
|
||||
self._numerator, self._denominator, exponent, no_neg_zero)
|
||||
scientific = False
|
||||
point_pos = precision
|
||||
else: # presentation_type in "eEgG"
|
||||
figures = (
|
||||
max(precision, 1)
|
||||
if presentation_type in "gG"
|
||||
else precision + 1
|
||||
)
|
||||
negative, significand, exponent = _round_to_figures(
|
||||
self._numerator, self._denominator, figures)
|
||||
scientific = (
|
||||
presentation_type in "eE"
|
||||
or exponent > 0
|
||||
or exponent + figures <= -4
|
||||
)
|
||||
point_pos = figures - 1 if scientific else -exponent
|
||||
|
||||
# Get the suffix - the part following the digits, if any.
|
||||
if presentation_type == "%":
|
||||
suffix = "%"
|
||||
elif scientific:
|
||||
suffix = f"{exponent_indicator}{exponent + point_pos:+03d}"
|
||||
else:
|
||||
suffix = ""
|
||||
|
||||
# String of output digits, padded sufficiently with zeros on the left
|
||||
# so that we'll have at least one digit before the decimal point.
|
||||
digits = f"{significand:0{point_pos + 1}d}"
|
||||
|
||||
# Before padding, the output has the form f"{sign}{leading}{trailing}",
|
||||
# where `leading` includes thousands separators if necessary and
|
||||
# `trailing` includes the decimal separator where appropriate.
|
||||
sign = "-" if negative else pos_sign
|
||||
leading = digits[: len(digits) - point_pos]
|
||||
frac_part = digits[len(digits) - point_pos :]
|
||||
if trim_zeros:
|
||||
frac_part = frac_part.rstrip("0")
|
||||
separator = "" if trim_point and not frac_part else "."
|
||||
trailing = separator + frac_part + suffix
|
||||
|
||||
# Do zero padding if required.
|
||||
if zeropad:
|
||||
min_leading = minimumwidth - len(sign) - len(trailing)
|
||||
# When adding thousands separators, they'll be added to the
|
||||
# zero-padded portion too, so we need to compensate.
|
||||
leading = leading.zfill(
|
||||
3 * min_leading // 4 + 1 if thousands_sep else min_leading
|
||||
)
|
||||
|
||||
# Insert thousands separators if required.
|
||||
if thousands_sep:
|
||||
first_pos = 1 + (len(leading) - 1) % 3
|
||||
leading = leading[:first_pos] + "".join(
|
||||
thousands_sep + leading[pos : pos + 3]
|
||||
for pos in range(first_pos, len(leading), 3)
|
||||
)
|
||||
|
||||
# We now have a sign and a body. Pad with fill character if necessary
|
||||
# and return.
|
||||
body = leading + trailing
|
||||
padding = fill * (minimumwidth - len(sign) - len(body))
|
||||
if align == ">":
|
||||
return padding + sign + body
|
||||
elif align == "<":
|
||||
return sign + body + padding
|
||||
elif align == "^":
|
||||
half = len(padding) // 2
|
||||
return padding[:half] + sign + body + padding[half:]
|
||||
else: # align == "="
|
||||
return sign + padding + body
|
||||
|
||||
def _operator_fallbacks(monomorphic_operator, fallback_operator):
|
||||
"""Generates forward and reverse operators given a purely-rational
|
||||
operator and a function from the operator module.
|
||||
@@ -380,8 +611,10 @@ class Fraction(numbers.Rational):
|
||||
|
||||
"""
|
||||
def forward(a, b):
|
||||
if isinstance(b, (int, Fraction)):
|
||||
if isinstance(b, Fraction):
|
||||
return monomorphic_operator(a, b)
|
||||
elif isinstance(b, int):
|
||||
return monomorphic_operator(a, Fraction(b))
|
||||
elif isinstance(b, float):
|
||||
return fallback_operator(float(a), b)
|
||||
elif isinstance(b, complex):
|
||||
@@ -394,7 +627,7 @@ class Fraction(numbers.Rational):
|
||||
def reverse(b, a):
|
||||
if isinstance(a, numbers.Rational):
|
||||
# Includes ints.
|
||||
return monomorphic_operator(a, b)
|
||||
return monomorphic_operator(Fraction(a), b)
|
||||
elif isinstance(a, numbers.Real):
|
||||
return fallback_operator(float(a), float(b))
|
||||
elif isinstance(a, numbers.Complex):
|
||||
@@ -406,32 +639,141 @@ class Fraction(numbers.Rational):
|
||||
|
||||
return forward, reverse
|
||||
|
||||
# Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
|
||||
#
|
||||
# Assume input fractions a and b are normalized.
|
||||
#
|
||||
# 1) Consider addition/subtraction.
|
||||
#
|
||||
# Let g = gcd(da, db). Then
|
||||
#
|
||||
# na nb na*db ± nb*da
|
||||
# a ± b == -- ± -- == ------------- ==
|
||||
# da db da*db
|
||||
#
|
||||
# na*(db//g) ± nb*(da//g) t
|
||||
# == ----------------------- == -
|
||||
# (da*db)//g d
|
||||
#
|
||||
# Now, if g > 1, we're working with smaller integers.
|
||||
#
|
||||
# Note, that t, (da//g) and (db//g) are pairwise coprime.
|
||||
#
|
||||
# Indeed, (da//g) and (db//g) share no common factors (they were
|
||||
# removed) and da is coprime with na (since input fractions are
|
||||
# normalized), hence (da//g) and na are coprime. By symmetry,
|
||||
# (db//g) and nb are coprime too. Then,
|
||||
#
|
||||
# gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
|
||||
# gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
|
||||
#
|
||||
# Above allows us optimize reduction of the result to lowest
|
||||
# terms. Indeed,
|
||||
#
|
||||
# g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
|
||||
#
|
||||
# t//g2 t//g2
|
||||
# a ± b == ----------------------- == ----------------
|
||||
# (da//g)*(db//g)*(g//g2) (da//g)*(db//g2)
|
||||
#
|
||||
# is a normalized fraction. This is useful because the unnormalized
|
||||
# denominator d could be much larger than g.
|
||||
#
|
||||
# We should special-case g == 1 (and g2 == 1), since 60.8% of
|
||||
# randomly-chosen integers are coprime:
|
||||
# https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
|
||||
# Note, that g2 == 1 always for fractions, obtained from floats: here
|
||||
# g is a power of 2 and the unnormalized numerator t is an odd integer.
|
||||
#
|
||||
# 2) Consider multiplication
|
||||
#
|
||||
# Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
|
||||
#
|
||||
# na*nb na*nb (na//g1)*(nb//g2)
|
||||
# a*b == ----- == ----- == -----------------
|
||||
# da*db db*da (db//g1)*(da//g2)
|
||||
#
|
||||
# Note, that after divisions we're multiplying smaller integers.
|
||||
#
|
||||
# Also, the resulting fraction is normalized, because each of
|
||||
# two factors in the numerator is coprime to each of the two factors
|
||||
# in the denominator.
|
||||
#
|
||||
# Indeed, pick (na//g1). It's coprime with (da//g2), because input
|
||||
# fractions are normalized. It's also coprime with (db//g1), because
|
||||
# common factors are removed by g1 == gcd(na, db).
|
||||
#
|
||||
# As for addition/subtraction, we should special-case g1 == 1
|
||||
# and g2 == 1 for same reason. That happens also for multiplying
|
||||
# rationals, obtained from floats.
|
||||
|
||||
def _add(a, b):
|
||||
"""a + b"""
|
||||
da, db = a.denominator, b.denominator
|
||||
return Fraction(a.numerator * db + b.numerator * da,
|
||||
da * db)
|
||||
na, da = a._numerator, a._denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
g = math.gcd(da, db)
|
||||
if g == 1:
|
||||
return Fraction._from_coprime_ints(na * db + da * nb, da * db)
|
||||
s = da // g
|
||||
t = na * (db // g) + nb * s
|
||||
g2 = math.gcd(t, g)
|
||||
if g2 == 1:
|
||||
return Fraction._from_coprime_ints(t, s * db)
|
||||
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
|
||||
|
||||
__add__, __radd__ = _operator_fallbacks(_add, operator.add)
|
||||
|
||||
def _sub(a, b):
|
||||
"""a - b"""
|
||||
da, db = a.denominator, b.denominator
|
||||
return Fraction(a.numerator * db - b.numerator * da,
|
||||
da * db)
|
||||
na, da = a._numerator, a._denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
g = math.gcd(da, db)
|
||||
if g == 1:
|
||||
return Fraction._from_coprime_ints(na * db - da * nb, da * db)
|
||||
s = da // g
|
||||
t = na * (db // g) - nb * s
|
||||
g2 = math.gcd(t, g)
|
||||
if g2 == 1:
|
||||
return Fraction._from_coprime_ints(t, s * db)
|
||||
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
|
||||
|
||||
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
|
||||
|
||||
def _mul(a, b):
|
||||
"""a * b"""
|
||||
return Fraction(a.numerator * b.numerator, a.denominator * b.denominator)
|
||||
na, da = a._numerator, a._denominator
|
||||
nb, db = b._numerator, b._denominator
|
||||
g1 = math.gcd(na, db)
|
||||
if g1 > 1:
|
||||
na //= g1
|
||||
db //= g1
|
||||
g2 = math.gcd(nb, da)
|
||||
if g2 > 1:
|
||||
nb //= g2
|
||||
da //= g2
|
||||
return Fraction._from_coprime_ints(na * nb, db * da)
|
||||
|
||||
__mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul)
|
||||
|
||||
def _div(a, b):
|
||||
"""a / b"""
|
||||
return Fraction(a.numerator * b.denominator,
|
||||
a.denominator * b.numerator)
|
||||
# Same as _mul(), with inversed b.
|
||||
nb, db = b._numerator, b._denominator
|
||||
if nb == 0:
|
||||
raise ZeroDivisionError('Fraction(%s, 0)' % db)
|
||||
na, da = a._numerator, a._denominator
|
||||
g1 = math.gcd(na, nb)
|
||||
if g1 > 1:
|
||||
na //= g1
|
||||
nb //= g1
|
||||
g2 = math.gcd(db, da)
|
||||
if g2 > 1:
|
||||
da //= g2
|
||||
db //= g2
|
||||
n, d = na * db, nb * da
|
||||
if d < 0:
|
||||
n, d = -n, -d
|
||||
return Fraction._from_coprime_ints(n, d)
|
||||
|
||||
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
|
||||
|
||||
@@ -468,17 +810,17 @@ class Fraction(numbers.Rational):
|
||||
if b.denominator == 1:
|
||||
power = b.numerator
|
||||
if power >= 0:
|
||||
return Fraction(a._numerator ** power,
|
||||
a._denominator ** power,
|
||||
_normalize=False)
|
||||
elif a._numerator >= 0:
|
||||
return Fraction(a._denominator ** -power,
|
||||
a._numerator ** -power,
|
||||
_normalize=False)
|
||||
return Fraction._from_coprime_ints(a._numerator ** power,
|
||||
a._denominator ** power)
|
||||
elif a._numerator > 0:
|
||||
return Fraction._from_coprime_ints(a._denominator ** -power,
|
||||
a._numerator ** -power)
|
||||
elif a._numerator == 0:
|
||||
raise ZeroDivisionError('Fraction(%s, 0)' %
|
||||
a._denominator ** -power)
|
||||
else:
|
||||
return Fraction((-a._denominator) ** -power,
|
||||
(-a._numerator) ** -power,
|
||||
_normalize=False)
|
||||
return Fraction._from_coprime_ints((-a._denominator) ** -power,
|
||||
(-a._numerator) ** -power)
|
||||
else:
|
||||
# A fractional power will generally produce an
|
||||
# irrational number.
|
||||
@@ -502,18 +844,25 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def __pos__(a):
|
||||
"""+a: Coerces a subclass instance to Fraction"""
|
||||
return Fraction(a._numerator, a._denominator, _normalize=False)
|
||||
return Fraction._from_coprime_ints(a._numerator, a._denominator)
|
||||
|
||||
def __neg__(a):
|
||||
"""-a"""
|
||||
return Fraction(-a._numerator, a._denominator, _normalize=False)
|
||||
return Fraction._from_coprime_ints(-a._numerator, a._denominator)
|
||||
|
||||
def __abs__(a):
|
||||
"""abs(a)"""
|
||||
return Fraction(abs(a._numerator), a._denominator, _normalize=False)
|
||||
return Fraction._from_coprime_ints(abs(a._numerator), a._denominator)
|
||||
|
||||
def __int__(a, _index=operator.index):
|
||||
"""int(a)"""
|
||||
if a._numerator < 0:
|
||||
return _index(-(-a._numerator // a._denominator))
|
||||
else:
|
||||
return _index(a._numerator // a._denominator)
|
||||
|
||||
def __trunc__(a):
|
||||
"""trunc(a)"""
|
||||
"""math.trunc(a)"""
|
||||
if a._numerator < 0:
|
||||
return -(-a._numerator // a._denominator)
|
||||
else:
|
||||
@@ -521,12 +870,12 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def __floor__(a):
|
||||
"""math.floor(a)"""
|
||||
return a.numerator // a.denominator
|
||||
return a._numerator // a._denominator
|
||||
|
||||
def __ceil__(a):
|
||||
"""math.ceil(a)"""
|
||||
# The negations cleverly convince floordiv to return the ceiling.
|
||||
return -(-a.numerator // a.denominator)
|
||||
return -(-a._numerator // a._denominator)
|
||||
|
||||
def __round__(self, ndigits=None):
|
||||
"""round(self, ndigits)
|
||||
@@ -534,10 +883,11 @@ class Fraction(numbers.Rational):
|
||||
Rounds half toward even.
|
||||
"""
|
||||
if ndigits is None:
|
||||
floor, remainder = divmod(self.numerator, self.denominator)
|
||||
if remainder * 2 < self.denominator:
|
||||
d = self._denominator
|
||||
floor, remainder = divmod(self._numerator, d)
|
||||
if remainder * 2 < d:
|
||||
return floor
|
||||
elif remainder * 2 > self.denominator:
|
||||
elif remainder * 2 > d:
|
||||
return floor + 1
|
||||
# Deal with the half case:
|
||||
elif floor % 2 == 0:
|
||||
@@ -555,25 +905,7 @@ class Fraction(numbers.Rational):
|
||||
|
||||
def __hash__(self):
|
||||
"""hash(self)"""
|
||||
|
||||
# XXX since this method is expensive, consider caching the result
|
||||
|
||||
# In order to make sure that the hash of a Fraction agrees
|
||||
# with the hash of a numerically equal integer, float or
|
||||
# Decimal instance, we follow the rules for numeric hashes
|
||||
# outlined in the documentation. (See library docs, 'Built-in
|
||||
# Types').
|
||||
|
||||
# dinv is the inverse of self._denominator modulo the prime
|
||||
# _PyHASH_MODULUS, or 0 if self._denominator is divisible by
|
||||
# _PyHASH_MODULUS.
|
||||
dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
|
||||
if not dinv:
|
||||
hash_ = _PyHASH_INF
|
||||
else:
|
||||
hash_ = abs(self._numerator) * dinv % _PyHASH_MODULUS
|
||||
result = hash_ if self >= 0 else -hash_
|
||||
return -2 if result == -1 else result
|
||||
return _hash_algorithm(self._numerator, self._denominator)
|
||||
|
||||
def __eq__(a, b):
|
||||
"""a == b"""
|
||||
@@ -643,7 +975,7 @@ class Fraction(numbers.Rational):
|
||||
# support for pickling, copy, and deepcopy
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (str(self),))
|
||||
return (self.__class__, (self._numerator, self._denominator))
|
||||
|
||||
def __copy__(self):
|
||||
if type(self) == Fraction:
|
||||
|
||||
85
Lib/ftplib.py
vendored
85
Lib/ftplib.py
vendored
@@ -72,17 +72,17 @@ B_CRLF = b'\r\n'
|
||||
|
||||
# The class itself
|
||||
class FTP:
|
||||
|
||||
'''An FTP client class.
|
||||
|
||||
To create a connection, call the class using these arguments:
|
||||
host, user, passwd, acct, timeout
|
||||
host, user, passwd, acct, timeout, source_address, encoding
|
||||
|
||||
The first four arguments are all strings, and have default value ''.
|
||||
timeout must be numeric and defaults to None if not passed,
|
||||
meaning that no timeout will be set on any ftp socket(s)
|
||||
The parameter ´timeout´ must be numeric and defaults to None if not
|
||||
passed, meaning that no timeout will be set on any ftp socket(s).
|
||||
If a timeout is passed, then this is now the default timeout for all ftp
|
||||
socket operations for this instance.
|
||||
The last parameter is the encoding of filenames, which defaults to utf-8.
|
||||
|
||||
Then use self.connect() with optional host and port argument.
|
||||
|
||||
@@ -102,15 +102,19 @@ class FTP:
|
||||
sock = None
|
||||
file = None
|
||||
welcome = None
|
||||
passiveserver = 1
|
||||
encoding = "latin-1"
|
||||
passiveserver = True
|
||||
# Disables https://bugs.python.org/issue43285 security if set to True.
|
||||
trust_server_pasv_ipv4_address = False
|
||||
|
||||
# Initialization method (called by class instantiation).
|
||||
# Initialize host to localhost, port to standard ftp port
|
||||
# Optional arguments are host (for connect()),
|
||||
# and user, passwd, acct (for login())
|
||||
def __init__(self, host='', user='', passwd='', acct='',
|
||||
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None):
|
||||
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
|
||||
encoding='utf-8'):
|
||||
"""Initialization method (called by class instantiation).
|
||||
Initialize host to localhost, port to standard ftp port.
|
||||
Optional arguments are host (for connect()),
|
||||
and user, passwd, acct (for login()).
|
||||
"""
|
||||
self.encoding = encoding
|
||||
self.source_address = source_address
|
||||
self.timeout = timeout
|
||||
if host:
|
||||
@@ -146,6 +150,8 @@ class FTP:
|
||||
self.port = port
|
||||
if timeout != -999:
|
||||
self.timeout = timeout
|
||||
if self.timeout is not None and not self.timeout:
|
||||
raise ValueError('Non-blocking socket (timeout=0) is not supported')
|
||||
if source_address is not None:
|
||||
self.source_address = source_address
|
||||
sys.audit("ftplib.connect", self, self.host, self.port)
|
||||
@@ -316,8 +322,13 @@ class FTP:
|
||||
return sock
|
||||
|
||||
def makepasv(self):
|
||||
"""Internal: Does the PASV or EPSV handshake -> (address, port)"""
|
||||
if self.af == socket.AF_INET:
|
||||
host, port = parse227(self.sendcmd('PASV'))
|
||||
untrusted_host, port = parse227(self.sendcmd('PASV'))
|
||||
if self.trust_server_pasv_ipv4_address:
|
||||
host = untrusted_host
|
||||
else:
|
||||
host = self.sock.getpeername()[0]
|
||||
else:
|
||||
host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
|
||||
return host, port
|
||||
@@ -423,10 +434,7 @@ class FTP:
|
||||
"""
|
||||
self.voidcmd('TYPE I')
|
||||
with self.transfercmd(cmd, rest) as conn:
|
||||
while 1:
|
||||
data = conn.recv(blocksize)
|
||||
if not data:
|
||||
break
|
||||
while data := conn.recv(blocksize):
|
||||
callback(data)
|
||||
# shutdown ssl layer
|
||||
if _SSLSocket is not None and isinstance(conn, _SSLSocket):
|
||||
@@ -485,10 +493,7 @@ class FTP:
|
||||
"""
|
||||
self.voidcmd('TYPE I')
|
||||
with self.transfercmd(cmd, rest) as conn:
|
||||
while 1:
|
||||
buf = fp.read(blocksize)
|
||||
if not buf:
|
||||
break
|
||||
while buf := fp.read(blocksize):
|
||||
conn.sendall(buf)
|
||||
if callback:
|
||||
callback(buf)
|
||||
@@ -550,7 +555,7 @@ class FTP:
|
||||
LIST command. (This *should* only be used for a pathname.)'''
|
||||
cmd = 'LIST'
|
||||
func = None
|
||||
if args[-1:] and type(args[-1]) != type(''):
|
||||
if args[-1:] and not isinstance(args[-1], str):
|
||||
args, func = args[:-1], args[-1]
|
||||
for arg in args:
|
||||
if arg:
|
||||
@@ -702,46 +707,31 @@ else:
|
||||
'221 Goodbye.'
|
||||
>>>
|
||||
'''
|
||||
ssl_version = ssl.PROTOCOL_TLS_CLIENT
|
||||
|
||||
def __init__(self, host='', user='', passwd='', acct='', keyfile=None,
|
||||
certfile=None, context=None,
|
||||
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None):
|
||||
if context is not None and keyfile is not None:
|
||||
raise ValueError("context and keyfile arguments are mutually "
|
||||
"exclusive")
|
||||
if context is not None and certfile is not None:
|
||||
raise ValueError("context and certfile arguments are mutually "
|
||||
"exclusive")
|
||||
if keyfile is not None or certfile is not None:
|
||||
import warnings
|
||||
warnings.warn("keyfile and certfile are deprecated, use a "
|
||||
"custom context instead", DeprecationWarning, 2)
|
||||
self.keyfile = keyfile
|
||||
self.certfile = certfile
|
||||
def __init__(self, host='', user='', passwd='', acct='',
|
||||
*, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None, encoding='utf-8'):
|
||||
if context is None:
|
||||
context = ssl._create_stdlib_context(self.ssl_version,
|
||||
certfile=certfile,
|
||||
keyfile=keyfile)
|
||||
context = ssl._create_stdlib_context()
|
||||
self.context = context
|
||||
self._prot_p = False
|
||||
FTP.__init__(self, host, user, passwd, acct, timeout, source_address)
|
||||
super().__init__(host, user, passwd, acct,
|
||||
timeout, source_address, encoding=encoding)
|
||||
|
||||
def login(self, user='', passwd='', acct='', secure=True):
|
||||
if secure and not isinstance(self.sock, ssl.SSLSocket):
|
||||
self.auth()
|
||||
return FTP.login(self, user, passwd, acct)
|
||||
return super().login(user, passwd, acct)
|
||||
|
||||
def auth(self):
|
||||
'''Set up secure control connection by using TLS/SSL.'''
|
||||
if isinstance(self.sock, ssl.SSLSocket):
|
||||
raise ValueError("Already using TLS")
|
||||
if self.ssl_version >= ssl.PROTOCOL_TLS:
|
||||
if self.context.protocol >= ssl.PROTOCOL_TLS:
|
||||
resp = self.voidcmd('AUTH TLS')
|
||||
else:
|
||||
resp = self.voidcmd('AUTH SSL')
|
||||
self.sock = self.context.wrap_socket(self.sock,
|
||||
server_hostname=self.host)
|
||||
self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
|
||||
self.file = self.sock.makefile(mode='r', encoding=self.encoding)
|
||||
return resp
|
||||
|
||||
@@ -778,7 +768,7 @@ else:
|
||||
# --- Overridden FTP methods
|
||||
|
||||
def ntransfercmd(self, cmd, rest=None):
|
||||
conn, size = FTP.ntransfercmd(self, cmd, rest)
|
||||
conn, size = super().ntransfercmd(cmd, rest)
|
||||
if self._prot_p:
|
||||
conn = self.context.wrap_socket(conn,
|
||||
server_hostname=self.host)
|
||||
@@ -823,7 +813,6 @@ def parse227(resp):
|
||||
'''Parse the '227' response for a PASV request.
|
||||
Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
|
||||
Return ('host.addr.as.numbers', port#) tuple.'''
|
||||
|
||||
if resp[:3] != '227':
|
||||
raise error_reply(resp)
|
||||
global _227_re
|
||||
@@ -843,7 +832,6 @@ def parse229(resp, peer):
|
||||
'''Parse the '229' response for an EPSV request.
|
||||
Raises error_proto if it does not contain '(|||port|)'
|
||||
Return ('host.addr.as.numbers', port#) tuple.'''
|
||||
|
||||
if resp[:3] != '229':
|
||||
raise error_reply(resp)
|
||||
left = resp.find('(')
|
||||
@@ -865,7 +853,6 @@ def parse257(resp):
|
||||
'''Parse the '257' response for a MKD or PWD request.
|
||||
This is a response to a MKD or PWD request: a directory name.
|
||||
Returns the directoryname in the 257 reply.'''
|
||||
|
||||
if resp[:3] != '257':
|
||||
raise error_reply(resp)
|
||||
if resp[3:5] != ' "':
|
||||
|
||||
183
Lib/functools.py
vendored
183
Lib/functools.py
vendored
@@ -10,9 +10,9 @@
|
||||
# See C source code for _functools credits/copyright
|
||||
|
||||
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
|
||||
'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial',
|
||||
'partialmethod', 'singledispatch', 'singledispatchmethod',
|
||||
"cached_property"]
|
||||
'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce',
|
||||
'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
|
||||
'cached_property']
|
||||
|
||||
from abc import get_cache_token
|
||||
from collections import namedtuple
|
||||
@@ -30,7 +30,7 @@ from types import GenericAlias
|
||||
# wrapper functions that can handle naive introspection
|
||||
|
||||
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
|
||||
'__annotations__')
|
||||
'__annotations__', '__type_params__')
|
||||
WRAPPER_UPDATES = ('__dict__',)
|
||||
def update_wrapper(wrapper,
|
||||
wrapped,
|
||||
@@ -86,82 +86,86 @@ def wraps(wrapped,
|
||||
# infinite recursion that could occur when the operator dispatch logic
|
||||
# detects a NotImplemented result and then calls a reflected method.
|
||||
|
||||
def _gt_from_lt(self, other, NotImplemented=NotImplemented):
|
||||
def _gt_from_lt(self, other):
|
||||
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
|
||||
op_result = self.__lt__(other)
|
||||
op_result = type(self).__lt__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result and self != other
|
||||
|
||||
def _le_from_lt(self, other, NotImplemented=NotImplemented):
|
||||
def _le_from_lt(self, other):
|
||||
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
|
||||
op_result = self.__lt__(other)
|
||||
op_result = type(self).__lt__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return op_result or self == other
|
||||
|
||||
def _ge_from_lt(self, other, NotImplemented=NotImplemented):
|
||||
def _ge_from_lt(self, other):
|
||||
'Return a >= b. Computed by @total_ordering from (not a < b).'
|
||||
op_result = self.__lt__(other)
|
||||
op_result = type(self).__lt__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
def _ge_from_le(self, other, NotImplemented=NotImplemented):
|
||||
def _ge_from_le(self, other):
|
||||
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
|
||||
op_result = self.__le__(other)
|
||||
op_result = type(self).__le__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result or self == other
|
||||
|
||||
def _lt_from_le(self, other, NotImplemented=NotImplemented):
|
||||
def _lt_from_le(self, other):
|
||||
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
|
||||
op_result = self.__le__(other)
|
||||
op_result = type(self).__le__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return op_result and self != other
|
||||
|
||||
def _gt_from_le(self, other, NotImplemented=NotImplemented):
|
||||
def _gt_from_le(self, other):
|
||||
'Return a > b. Computed by @total_ordering from (not a <= b).'
|
||||
op_result = self.__le__(other)
|
||||
op_result = type(self).__le__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
def _lt_from_gt(self, other, NotImplemented=NotImplemented):
|
||||
def _lt_from_gt(self, other):
|
||||
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
|
||||
op_result = self.__gt__(other)
|
||||
op_result = type(self).__gt__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result and self != other
|
||||
|
||||
def _ge_from_gt(self, other, NotImplemented=NotImplemented):
|
||||
def _ge_from_gt(self, other):
|
||||
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
|
||||
op_result = self.__gt__(other)
|
||||
op_result = type(self).__gt__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return op_result or self == other
|
||||
|
||||
def _le_from_gt(self, other, NotImplemented=NotImplemented):
|
||||
def _le_from_gt(self, other):
|
||||
'Return a <= b. Computed by @total_ordering from (not a > b).'
|
||||
op_result = self.__gt__(other)
|
||||
op_result = type(self).__gt__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
|
||||
def _le_from_ge(self, other, NotImplemented=NotImplemented):
|
||||
def _le_from_ge(self, other):
|
||||
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
|
||||
op_result = self.__ge__(other)
|
||||
op_result = type(self).__ge__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result or self == other
|
||||
|
||||
def _gt_from_ge(self, other, NotImplemented=NotImplemented):
|
||||
def _gt_from_ge(self, other):
|
||||
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
|
||||
op_result = self.__ge__(other)
|
||||
op_result = type(self).__ge__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return op_result and self != other
|
||||
|
||||
def _lt_from_ge(self, other, NotImplemented=NotImplemented):
|
||||
def _lt_from_ge(self, other):
|
||||
'Return a < b. Computed by @total_ordering from (not a >= b).'
|
||||
op_result = self.__ge__(other)
|
||||
op_result = type(self).__ge__(self, other)
|
||||
if op_result is NotImplemented:
|
||||
return op_result
|
||||
return not op_result
|
||||
@@ -232,14 +236,14 @@ _initial_missing = object()
|
||||
|
||||
def reduce(function, sequence, initial=_initial_missing):
|
||||
"""
|
||||
reduce(function, sequence[, initial]) -> value
|
||||
reduce(function, iterable[, initial]) -> value
|
||||
|
||||
Apply a function of two arguments cumulatively to the items of a sequence,
|
||||
from left to right, so as to reduce the sequence to a single value.
|
||||
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
|
||||
Apply a function of two arguments cumulatively to the items of a sequence
|
||||
or iterable, from left to right, so as to reduce the iterable to a single
|
||||
value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
|
||||
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
|
||||
of the sequence in the calculation, and serves as a default when the
|
||||
sequence is empty.
|
||||
of the iterable in the calculation, and serves as a default when the
|
||||
iterable is empty.
|
||||
"""
|
||||
|
||||
it = iter(sequence)
|
||||
@@ -248,7 +252,8 @@ def reduce(function, sequence, initial=_initial_missing):
|
||||
try:
|
||||
value = next(it)
|
||||
except StopIteration:
|
||||
raise TypeError("reduce() of empty sequence with no initial value") from None
|
||||
raise TypeError(
|
||||
"reduce() of empty iterable with no initial value") from None
|
||||
else:
|
||||
value = initial
|
||||
|
||||
@@ -347,23 +352,7 @@ class partialmethod(object):
|
||||
callables as instance methods.
|
||||
"""
|
||||
|
||||
def __init__(*args, **keywords):
|
||||
if len(args) >= 2:
|
||||
self, func, *args = args
|
||||
elif not args:
|
||||
raise TypeError("descriptor '__init__' of partialmethod "
|
||||
"needs an argument")
|
||||
elif 'func' in keywords:
|
||||
func = keywords.pop('func')
|
||||
self, *args = args
|
||||
import warnings
|
||||
warnings.warn("Passing 'func' as keyword argument is deprecated",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
else:
|
||||
raise TypeError("type 'partialmethod' takes at least one argument, "
|
||||
"got %d" % (len(args)-1))
|
||||
args = tuple(args)
|
||||
|
||||
def __init__(self, func, /, *args, **keywords):
|
||||
if not callable(func) and not hasattr(func, "__get__"):
|
||||
raise TypeError("{!r} is not callable or a descriptor"
|
||||
.format(func))
|
||||
@@ -381,7 +370,6 @@ class partialmethod(object):
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.keywords = keywords
|
||||
__init__.__text_signature__ = '($self, func, /, *args, **keywords)'
|
||||
|
||||
def __repr__(self):
|
||||
args = ", ".join(map(repr, self.args))
|
||||
@@ -427,6 +415,7 @@ class partialmethod(object):
|
||||
|
||||
__class_getitem__ = classmethod(GenericAlias)
|
||||
|
||||
|
||||
# Helper functions
|
||||
|
||||
def _unwrap_partial(func):
|
||||
@@ -503,7 +492,7 @@ def lru_cache(maxsize=128, typed=False):
|
||||
with f.cache_info(). Clear the cache and statistics with f.cache_clear().
|
||||
Access the underlying function with f.__wrapped__.
|
||||
|
||||
See: http://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
|
||||
See: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
|
||||
|
||||
"""
|
||||
|
||||
@@ -520,6 +509,7 @@ def lru_cache(maxsize=128, typed=False):
|
||||
# The user_function was passed in directly via the maxsize argument
|
||||
user_function, maxsize = maxsize, 128
|
||||
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
|
||||
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
|
||||
return update_wrapper(wrapper, user_function)
|
||||
elif maxsize is not None:
|
||||
raise TypeError(
|
||||
@@ -527,6 +517,7 @@ def lru_cache(maxsize=128, typed=False):
|
||||
|
||||
def decorating_function(user_function):
|
||||
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
|
||||
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
|
||||
return update_wrapper(wrapper, user_function)
|
||||
|
||||
return decorating_function
|
||||
@@ -653,6 +644,15 @@ except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
################################################################################
|
||||
### cache -- simplified access to the infinity cache
|
||||
################################################################################
|
||||
|
||||
def cache(user_function, /):
|
||||
'Simple lightweight unbounded cache. Sometimes called "memoize".'
|
||||
return lru_cache(maxsize=None)(user_function)
|
||||
|
||||
|
||||
################################################################################
|
||||
### singledispatch() - single-dispatch generic function decorator
|
||||
################################################################################
|
||||
@@ -660,7 +660,7 @@ except ImportError:
|
||||
def _c3_merge(sequences):
|
||||
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
|
||||
|
||||
Adapted from http://www.python.org/download/releases/2.3/mro/.
|
||||
Adapted from https://www.python.org/download/releases/2.3/mro/.
|
||||
|
||||
"""
|
||||
result = []
|
||||
@@ -740,6 +740,7 @@ def _compose_mro(cls, types):
|
||||
# Remove entries which are already present in the __mro__ or unrelated.
|
||||
def is_related(typ):
|
||||
return (typ not in bases and hasattr(typ, '__mro__')
|
||||
and not isinstance(typ, GenericAlias)
|
||||
and issubclass(cls, typ))
|
||||
types = [n for n in types if is_related(n)]
|
||||
# Remove entries which are strict bases of other entries (they will end up
|
||||
@@ -837,6 +838,17 @@ def singledispatch(func):
|
||||
dispatch_cache[cls] = impl
|
||||
return impl
|
||||
|
||||
def _is_union_type(cls):
|
||||
from typing import get_origin, Union
|
||||
return get_origin(cls) in {Union, types.UnionType}
|
||||
|
||||
def _is_valid_dispatch_type(cls):
|
||||
if isinstance(cls, type):
|
||||
return True
|
||||
from typing import get_args
|
||||
return (_is_union_type(cls) and
|
||||
all(isinstance(arg, type) for arg in get_args(cls)))
|
||||
|
||||
def register(cls, func=None):
|
||||
"""generic_func.register(cls, func) -> func
|
||||
|
||||
@@ -844,9 +856,15 @@ def singledispatch(func):
|
||||
|
||||
"""
|
||||
nonlocal cache_token
|
||||
if func is None:
|
||||
if isinstance(cls, type):
|
||||
if _is_valid_dispatch_type(cls):
|
||||
if func is None:
|
||||
return lambda f: register(cls, f)
|
||||
else:
|
||||
if func is not None:
|
||||
raise TypeError(
|
||||
f"Invalid first argument to `register()`. "
|
||||
f"{cls!r} is not a class or union type."
|
||||
)
|
||||
ann = getattr(cls, '__annotations__', {})
|
||||
if not ann:
|
||||
raise TypeError(
|
||||
@@ -859,12 +877,25 @@ def singledispatch(func):
|
||||
# only import typing if annotation parsing is necessary
|
||||
from typing import get_type_hints
|
||||
argname, cls = next(iter(get_type_hints(func).items()))
|
||||
if not isinstance(cls, type):
|
||||
raise TypeError(
|
||||
f"Invalid annotation for {argname!r}. "
|
||||
f"{cls!r} is not a class."
|
||||
)
|
||||
registry[cls] = func
|
||||
if not _is_valid_dispatch_type(cls):
|
||||
if _is_union_type(cls):
|
||||
raise TypeError(
|
||||
f"Invalid annotation for {argname!r}. "
|
||||
f"{cls!r} not all arguments are classes."
|
||||
)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Invalid annotation for {argname!r}. "
|
||||
f"{cls!r} is not a class."
|
||||
)
|
||||
|
||||
if _is_union_type(cls):
|
||||
from typing import get_args
|
||||
|
||||
for arg in get_args(cls):
|
||||
registry[arg] = func
|
||||
else:
|
||||
registry[cls] = func
|
||||
if cache_token is None and hasattr(cls, '__abstractmethods__'):
|
||||
cache_token = get_cache_token()
|
||||
dispatch_cache.clear()
|
||||
@@ -925,18 +956,16 @@ class singledispatchmethod:
|
||||
|
||||
|
||||
################################################################################
|
||||
### cached_property() - computed once per instance, cached as attribute
|
||||
### cached_property() - property result cached as instance attribute
|
||||
################################################################################
|
||||
|
||||
_NOT_FOUND = object()
|
||||
|
||||
|
||||
class cached_property:
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
self.attrname = None
|
||||
self.__doc__ = func.__doc__
|
||||
self.lock = RLock()
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
if self.attrname is None:
|
||||
@@ -963,19 +992,15 @@ class cached_property:
|
||||
raise TypeError(msg) from None
|
||||
val = cache.get(self.attrname, _NOT_FOUND)
|
||||
if val is _NOT_FOUND:
|
||||
with self.lock:
|
||||
# check if another thread filled cache while we awaited lock
|
||||
val = cache.get(self.attrname, _NOT_FOUND)
|
||||
if val is _NOT_FOUND:
|
||||
val = self.func(instance)
|
||||
try:
|
||||
cache[self.attrname] = val
|
||||
except TypeError:
|
||||
msg = (
|
||||
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
|
||||
f"does not support item assignment for caching {self.attrname!r} property."
|
||||
)
|
||||
raise TypeError(msg) from None
|
||||
val = self.func(instance)
|
||||
try:
|
||||
cache[self.attrname] = val
|
||||
except TypeError:
|
||||
msg = (
|
||||
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
|
||||
f"does not support item assignment for caching {self.attrname!r} property."
|
||||
)
|
||||
raise TypeError(msg) from None
|
||||
return val
|
||||
|
||||
__class_getitem__ = classmethod(GenericAlias)
|
||||
|
||||
19
Lib/genericpath.py
vendored
19
Lib/genericpath.py
vendored
@@ -3,14 +3,11 @@ Path operations common to more than one OS
|
||||
Do not use directly. The OS specific modules import the appropriate
|
||||
functions from this module themselves.
|
||||
"""
|
||||
try:
|
||||
import os
|
||||
except ImportError:
|
||||
import _dummy_os as os
|
||||
import os
|
||||
import stat
|
||||
|
||||
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
|
||||
'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile',
|
||||
'getsize', 'isdir', 'isfile', 'islink', 'samefile', 'sameopenfile',
|
||||
'samestat']
|
||||
|
||||
|
||||
@@ -48,6 +45,18 @@ def isdir(s):
|
||||
return stat.S_ISDIR(st.st_mode)
|
||||
|
||||
|
||||
# Is a path a symbolic link?
|
||||
# This will always return false on systems where os.lstat doesn't exist.
|
||||
|
||||
def islink(path):
|
||||
"""Test whether a path is a symbolic link"""
|
||||
try:
|
||||
st = os.lstat(path)
|
||||
except (OSError, ValueError, AttributeError):
|
||||
return False
|
||||
return stat.S_ISLNK(st.st_mode)
|
||||
|
||||
|
||||
def getsize(filename):
|
||||
"""Return the size of a file, reported by os.stat()."""
|
||||
return os.stat(filename).st_size
|
||||
|
||||
2
Lib/getopt.py
vendored
2
Lib/getopt.py
vendored
@@ -81,7 +81,7 @@ def getopt(args, shortopts, longopts = []):
|
||||
"""
|
||||
|
||||
opts = []
|
||||
if type(longopts) == type(""):
|
||||
if isinstance(longopts, str):
|
||||
longopts = [longopts]
|
||||
else:
|
||||
longopts = list(longopts)
|
||||
|
||||
194
Lib/gettext.py
vendored
194
Lib/gettext.py
vendored
@@ -46,17 +46,16 @@ internationalized, to the local language and cultural habits.
|
||||
# find this format documented anywhere.
|
||||
|
||||
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
__all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
|
||||
'find', 'translation', 'install', 'textdomain', 'bindtextdomain',
|
||||
'bind_textdomain_codeset',
|
||||
'dgettext', 'dngettext', 'gettext', 'lgettext', 'ldgettext',
|
||||
'ldngettext', 'lngettext', 'ngettext',
|
||||
'bindtextdomain', 'find', 'translation', 'install',
|
||||
'textdomain', 'dgettext', 'dngettext', 'gettext',
|
||||
'ngettext', 'pgettext', 'dpgettext', 'npgettext',
|
||||
'dnpgettext'
|
||||
]
|
||||
|
||||
_default_localedir = os.path.join(sys.base_prefix, 'share', 'locale')
|
||||
@@ -83,6 +82,7 @@ _token_pattern = re.compile(r"""
|
||||
(?P<INVALID>\w+|.) # invalid token
|
||||
""", re.VERBOSE|re.DOTALL)
|
||||
|
||||
|
||||
def _tokenize(plural):
|
||||
for mo in re.finditer(_token_pattern, plural):
|
||||
kind = mo.lastgroup
|
||||
@@ -94,12 +94,14 @@ def _tokenize(plural):
|
||||
yield value
|
||||
yield ''
|
||||
|
||||
|
||||
def _error(value):
|
||||
if value:
|
||||
return ValueError('unexpected token in plural form: %s' % value)
|
||||
else:
|
||||
return ValueError('unexpected end of plural form')
|
||||
|
||||
|
||||
_binary_ops = (
|
||||
('||',),
|
||||
('&&',),
|
||||
@@ -111,6 +113,7 @@ _binary_ops = (
|
||||
_binary_ops = {op: i for i, ops in enumerate(_binary_ops, 1) for op in ops}
|
||||
_c2py_ops = {'||': 'or', '&&': 'and', '/': '//'}
|
||||
|
||||
|
||||
def _parse(tokens, priority=-1):
|
||||
result = ''
|
||||
nexttok = next(tokens)
|
||||
@@ -160,6 +163,7 @@ def _parse(tokens, priority=-1):
|
||||
|
||||
return result, nexttok
|
||||
|
||||
|
||||
def _as_int(n):
|
||||
try:
|
||||
i = round(n)
|
||||
@@ -172,6 +176,7 @@ def _as_int(n):
|
||||
DeprecationWarning, 4)
|
||||
return n
|
||||
|
||||
|
||||
def c2py(plural):
|
||||
"""Gets a C expression as used in PO files for plural forms and returns a
|
||||
Python function that implements an equivalent expression.
|
||||
@@ -209,6 +214,7 @@ def c2py(plural):
|
||||
|
||||
|
||||
def _expand_lang(loc):
|
||||
import locale
|
||||
loc = locale.normalize(loc)
|
||||
COMPONENT_CODESET = 1 << 0
|
||||
COMPONENT_TERRITORY = 1 << 1
|
||||
@@ -249,12 +255,10 @@ def _expand_lang(loc):
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
class NullTranslations:
|
||||
def __init__(self, fp=None):
|
||||
self._info = {}
|
||||
self._charset = None
|
||||
self._output_charset = None
|
||||
self._fallback = None
|
||||
if fp is not None:
|
||||
self._parse(fp)
|
||||
@@ -273,13 +277,6 @@ class NullTranslations:
|
||||
return self._fallback.gettext(message)
|
||||
return message
|
||||
|
||||
def lgettext(self, message):
|
||||
if self._fallback:
|
||||
return self._fallback.lgettext(message)
|
||||
if self._output_charset:
|
||||
return message.encode(self._output_charset)
|
||||
return message.encode(locale.getpreferredencoding())
|
||||
|
||||
def ngettext(self, msgid1, msgid2, n):
|
||||
if self._fallback:
|
||||
return self._fallback.ngettext(msgid1, msgid2, n)
|
||||
@@ -288,16 +285,18 @@ class NullTranslations:
|
||||
else:
|
||||
return msgid2
|
||||
|
||||
def lngettext(self, msgid1, msgid2, n):
|
||||
def pgettext(self, context, message):
|
||||
if self._fallback:
|
||||
return self._fallback.lngettext(msgid1, msgid2, n)
|
||||
return self._fallback.pgettext(context, message)
|
||||
return message
|
||||
|
||||
def npgettext(self, context, msgid1, msgid2, n):
|
||||
if self._fallback:
|
||||
return self._fallback.npgettext(context, msgid1, msgid2, n)
|
||||
if n == 1:
|
||||
tmsg = msgid1
|
||||
return msgid1
|
||||
else:
|
||||
tmsg = msgid2
|
||||
if self._output_charset:
|
||||
return tmsg.encode(self._output_charset)
|
||||
return tmsg.encode(locale.getpreferredencoding())
|
||||
return msgid2
|
||||
|
||||
def info(self):
|
||||
return self._info
|
||||
@@ -305,24 +304,13 @@ class NullTranslations:
|
||||
def charset(self):
|
||||
return self._charset
|
||||
|
||||
def output_charset(self):
|
||||
return self._output_charset
|
||||
|
||||
def set_output_charset(self, charset):
|
||||
self._output_charset = charset
|
||||
|
||||
def install(self, names=None):
|
||||
import builtins
|
||||
builtins.__dict__['_'] = self.gettext
|
||||
if hasattr(names, "__contains__"):
|
||||
if "gettext" in names:
|
||||
builtins.__dict__['gettext'] = builtins.__dict__['_']
|
||||
if "ngettext" in names:
|
||||
builtins.__dict__['ngettext'] = self.ngettext
|
||||
if "lgettext" in names:
|
||||
builtins.__dict__['lgettext'] = self.lgettext
|
||||
if "lngettext" in names:
|
||||
builtins.__dict__['lngettext'] = self.lngettext
|
||||
if names is not None:
|
||||
allowed = {'gettext', 'ngettext', 'npgettext', 'pgettext'}
|
||||
for name in allowed & set(names):
|
||||
builtins.__dict__[name] = getattr(self, name)
|
||||
|
||||
|
||||
class GNUTranslations(NullTranslations):
|
||||
@@ -330,6 +318,10 @@ class GNUTranslations(NullTranslations):
|
||||
LE_MAGIC = 0x950412de
|
||||
BE_MAGIC = 0xde120495
|
||||
|
||||
# The encoding of a msgctxt and a msgid in a .mo file is
|
||||
# msgctxt + "\x04" + msgid (gettext version >= 0.15)
|
||||
CONTEXT = "%s\x04%s"
|
||||
|
||||
# Acceptable .mo versions
|
||||
VERSIONS = (0, 1)
|
||||
|
||||
@@ -385,6 +377,9 @@ class GNUTranslations(NullTranslations):
|
||||
item = b_item.decode().strip()
|
||||
if not item:
|
||||
continue
|
||||
# Skip over comment lines:
|
||||
if item.startswith('#-#-#-#-#') and item.endswith('#-#-#-#-#'):
|
||||
continue
|
||||
k = v = None
|
||||
if ':' in item:
|
||||
k, v = item.split(':', 1)
|
||||
@@ -423,39 +418,16 @@ class GNUTranslations(NullTranslations):
|
||||
masteridx += 8
|
||||
transidx += 8
|
||||
|
||||
def lgettext(self, message):
|
||||
missing = object()
|
||||
tmsg = self._catalog.get(message, missing)
|
||||
if tmsg is missing:
|
||||
if self._fallback:
|
||||
return self._fallback.lgettext(message)
|
||||
tmsg = message
|
||||
if self._output_charset:
|
||||
return tmsg.encode(self._output_charset)
|
||||
return tmsg.encode(locale.getpreferredencoding())
|
||||
|
||||
def lngettext(self, msgid1, msgid2, n):
|
||||
try:
|
||||
tmsg = self._catalog[(msgid1, self.plural(n))]
|
||||
except KeyError:
|
||||
if self._fallback:
|
||||
return self._fallback.lngettext(msgid1, msgid2, n)
|
||||
if n == 1:
|
||||
tmsg = msgid1
|
||||
else:
|
||||
tmsg = msgid2
|
||||
if self._output_charset:
|
||||
return tmsg.encode(self._output_charset)
|
||||
return tmsg.encode(locale.getpreferredencoding())
|
||||
|
||||
def gettext(self, message):
|
||||
missing = object()
|
||||
tmsg = self._catalog.get(message, missing)
|
||||
if tmsg is missing:
|
||||
if self._fallback:
|
||||
return self._fallback.gettext(message)
|
||||
return message
|
||||
return tmsg
|
||||
tmsg = self._catalog.get((message, self.plural(1)), missing)
|
||||
if tmsg is not missing:
|
||||
return tmsg
|
||||
if self._fallback:
|
||||
return self._fallback.gettext(message)
|
||||
return message
|
||||
|
||||
def ngettext(self, msgid1, msgid2, n):
|
||||
try:
|
||||
@@ -469,6 +441,31 @@ class GNUTranslations(NullTranslations):
|
||||
tmsg = msgid2
|
||||
return tmsg
|
||||
|
||||
def pgettext(self, context, message):
|
||||
ctxt_msg_id = self.CONTEXT % (context, message)
|
||||
missing = object()
|
||||
tmsg = self._catalog.get(ctxt_msg_id, missing)
|
||||
if tmsg is missing:
|
||||
tmsg = self._catalog.get((ctxt_msg_id, self.plural(1)), missing)
|
||||
if tmsg is not missing:
|
||||
return tmsg
|
||||
if self._fallback:
|
||||
return self._fallback.pgettext(context, message)
|
||||
return message
|
||||
|
||||
def npgettext(self, context, msgid1, msgid2, n):
|
||||
ctxt_msg_id = self.CONTEXT % (context, msgid1)
|
||||
try:
|
||||
tmsg = self._catalog[ctxt_msg_id, self.plural(n)]
|
||||
except KeyError:
|
||||
if self._fallback:
|
||||
return self._fallback.npgettext(context, msgid1, msgid2, n)
|
||||
if n == 1:
|
||||
tmsg = msgid1
|
||||
else:
|
||||
tmsg = msgid2
|
||||
return tmsg
|
||||
|
||||
|
||||
# Locate a .mo file using the gettext strategy
|
||||
def find(domain, localedir=None, languages=None, all=False):
|
||||
@@ -507,12 +504,12 @@ def find(domain, localedir=None, languages=None, all=False):
|
||||
return result
|
||||
|
||||
|
||||
|
||||
# a mapping between absolute .mo file path and Translation object
|
||||
_translations = {}
|
||||
|
||||
|
||||
def translation(domain, localedir=None, languages=None,
|
||||
class_=None, fallback=False, codeset=None):
|
||||
class_=None, fallback=False):
|
||||
if class_ is None:
|
||||
class_ = GNUTranslations
|
||||
mofiles = find(domain, localedir, languages, all=True)
|
||||
@@ -538,8 +535,6 @@ def translation(domain, localedir=None, languages=None,
|
||||
# are not used.
|
||||
import copy
|
||||
t = copy.copy(t)
|
||||
if codeset:
|
||||
t.set_output_charset(codeset)
|
||||
if result is None:
|
||||
result = t
|
||||
else:
|
||||
@@ -547,16 +542,13 @@ def translation(domain, localedir=None, languages=None,
|
||||
return result
|
||||
|
||||
|
||||
def install(domain, localedir=None, codeset=None, names=None):
|
||||
t = translation(domain, localedir, fallback=True, codeset=codeset)
|
||||
def install(domain, localedir=None, *, names=None):
|
||||
t = translation(domain, localedir, fallback=True)
|
||||
t.install(names)
|
||||
|
||||
|
||||
|
||||
# a mapping b/w domains and locale directories
|
||||
_localedirs = {}
|
||||
# a mapping b/w domains and codesets
|
||||
_localecodesets = {}
|
||||
# current global domain, `messages' used for compatibility w/ GNU gettext
|
||||
_current_domain = 'messages'
|
||||
|
||||
@@ -575,33 +567,17 @@ def bindtextdomain(domain, localedir=None):
|
||||
return _localedirs.get(domain, _default_localedir)
|
||||
|
||||
|
||||
def bind_textdomain_codeset(domain, codeset=None):
|
||||
global _localecodesets
|
||||
if codeset is not None:
|
||||
_localecodesets[domain] = codeset
|
||||
return _localecodesets.get(domain)
|
||||
|
||||
|
||||
def dgettext(domain, message):
|
||||
try:
|
||||
t = translation(domain, _localedirs.get(domain, None),
|
||||
codeset=_localecodesets.get(domain))
|
||||
t = translation(domain, _localedirs.get(domain, None))
|
||||
except OSError:
|
||||
return message
|
||||
return t.gettext(message)
|
||||
|
||||
def ldgettext(domain, message):
|
||||
codeset = _localecodesets.get(domain)
|
||||
try:
|
||||
t = translation(domain, _localedirs.get(domain, None), codeset=codeset)
|
||||
except OSError:
|
||||
return message.encode(codeset or locale.getpreferredencoding())
|
||||
return t.lgettext(message)
|
||||
|
||||
def dngettext(domain, msgid1, msgid2, n):
|
||||
try:
|
||||
t = translation(domain, _localedirs.get(domain, None),
|
||||
codeset=_localecodesets.get(domain))
|
||||
t = translation(domain, _localedirs.get(domain, None))
|
||||
except OSError:
|
||||
if n == 1:
|
||||
return msgid1
|
||||
@@ -609,29 +585,41 @@ def dngettext(domain, msgid1, msgid2, n):
|
||||
return msgid2
|
||||
return t.ngettext(msgid1, msgid2, n)
|
||||
|
||||
def ldngettext(domain, msgid1, msgid2, n):
|
||||
codeset = _localecodesets.get(domain)
|
||||
|
||||
def dpgettext(domain, context, message):
|
||||
try:
|
||||
t = translation(domain, _localedirs.get(domain, None), codeset=codeset)
|
||||
t = translation(domain, _localedirs.get(domain, None))
|
||||
except OSError:
|
||||
return message
|
||||
return t.pgettext(context, message)
|
||||
|
||||
|
||||
def dnpgettext(domain, context, msgid1, msgid2, n):
|
||||
try:
|
||||
t = translation(domain, _localedirs.get(domain, None))
|
||||
except OSError:
|
||||
if n == 1:
|
||||
tmsg = msgid1
|
||||
return msgid1
|
||||
else:
|
||||
tmsg = msgid2
|
||||
return tmsg.encode(codeset or locale.getpreferredencoding())
|
||||
return t.lngettext(msgid1, msgid2, n)
|
||||
return msgid2
|
||||
return t.npgettext(context, msgid1, msgid2, n)
|
||||
|
||||
|
||||
def gettext(message):
|
||||
return dgettext(_current_domain, message)
|
||||
|
||||
def lgettext(message):
|
||||
return ldgettext(_current_domain, message)
|
||||
|
||||
def ngettext(msgid1, msgid2, n):
|
||||
return dngettext(_current_domain, msgid1, msgid2, n)
|
||||
|
||||
def lngettext(msgid1, msgid2, n):
|
||||
return ldngettext(_current_domain, msgid1, msgid2, n)
|
||||
|
||||
def pgettext(context, message):
|
||||
return dpgettext(_current_domain, context, message)
|
||||
|
||||
|
||||
def npgettext(context, msgid1, msgid2, n):
|
||||
return dnpgettext(_current_domain, context, msgid1, msgid2, n)
|
||||
|
||||
|
||||
# dcgettext() has been deemed unnecessary and is not implemented.
|
||||
|
||||
|
||||
3
Lib/glob.py
vendored
3
Lib/glob.py
vendored
@@ -132,7 +132,8 @@ def glob1(dirname, pattern):
|
||||
|
||||
def _glob2(dirname, pattern, dir_fd, dironly, include_hidden=False):
|
||||
assert _isrecursive(pattern)
|
||||
yield pattern[:0]
|
||||
if not dirname or _isdir(dirname, dir_fd):
|
||||
yield pattern[:0]
|
||||
yield from _rlistdir(dirname, dir_fd, dironly,
|
||||
include_hidden=include_hidden)
|
||||
|
||||
|
||||
46
Lib/importlib/__init__.py
vendored
46
Lib/importlib/__init__.py
vendored
@@ -70,41 +70,6 @@ def invalidate_caches():
|
||||
finder.invalidate_caches()
|
||||
|
||||
|
||||
def find_loader(name, path=None):
|
||||
"""Return the loader for the specified module.
|
||||
|
||||
This is a backward-compatible wrapper around find_spec().
|
||||
|
||||
This function is deprecated in favor of importlib.util.find_spec().
|
||||
|
||||
"""
|
||||
warnings.warn('Deprecated since Python 3.4 and slated for removal in '
|
||||
'Python 3.12; use importlib.util.find_spec() instead',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
try:
|
||||
loader = sys.modules[name].__loader__
|
||||
if loader is None:
|
||||
raise ValueError('{}.__loader__ is None'.format(name))
|
||||
else:
|
||||
return loader
|
||||
except KeyError:
|
||||
pass
|
||||
except AttributeError:
|
||||
raise ValueError('{}.__loader__ is not set'.format(name)) from None
|
||||
|
||||
spec = _bootstrap._find_spec(name, path)
|
||||
# We won't worry about malformed specs (missing attributes).
|
||||
if spec is None:
|
||||
return None
|
||||
if spec.loader is None:
|
||||
if spec.submodule_search_locations is None:
|
||||
raise ImportError('spec for {} missing loader'.format(name),
|
||||
name=name)
|
||||
raise ImportError('namespace packages do not have loaders',
|
||||
name=name)
|
||||
return spec.loader
|
||||
|
||||
|
||||
def import_module(name, package=None):
|
||||
"""Import a module.
|
||||
|
||||
@@ -116,9 +81,8 @@ def import_module(name, package=None):
|
||||
level = 0
|
||||
if name.startswith('.'):
|
||||
if not package:
|
||||
msg = ("the 'package' argument is required to perform a relative "
|
||||
"import for {!r}")
|
||||
raise TypeError(msg.format(name))
|
||||
raise TypeError("the 'package' argument is required to perform a "
|
||||
f"relative import for {name!r}")
|
||||
for character in name:
|
||||
if character != '.':
|
||||
break
|
||||
@@ -144,8 +108,7 @@ def reload(module):
|
||||
raise TypeError("reload() argument must be a module")
|
||||
|
||||
if sys.modules.get(name) is not module:
|
||||
msg = "module {} not in sys.modules"
|
||||
raise ImportError(msg.format(name), name=name)
|
||||
raise ImportError(f"module {name} not in sys.modules", name=name)
|
||||
if name in _RELOADING:
|
||||
return _RELOADING[name]
|
||||
_RELOADING[name] = module
|
||||
@@ -155,8 +118,7 @@ def reload(module):
|
||||
try:
|
||||
parent = sys.modules[parent_name]
|
||||
except KeyError:
|
||||
msg = "parent {!r} not in sys.modules"
|
||||
raise ImportError(msg.format(parent_name),
|
||||
raise ImportError(f"parent {parent_name!r} not in sys.modules",
|
||||
name=parent_name) from None
|
||||
else:
|
||||
pkgpath = parent.__path__
|
||||
|
||||
15
Lib/importlib/_abc.py
vendored
15
Lib/importlib/_abc.py
vendored
@@ -1,7 +1,6 @@
|
||||
"""Subset of importlib.abc used to reduce importlib.util imports."""
|
||||
from . import _bootstrap
|
||||
import abc
|
||||
import warnings
|
||||
|
||||
|
||||
class Loader(metaclass=abc.ABCMeta):
|
||||
@@ -38,17 +37,3 @@ class Loader(metaclass=abc.ABCMeta):
|
||||
raise ImportError
|
||||
# Warning implemented in _load_module_shim().
|
||||
return _bootstrap._load_module_shim(self, fullname)
|
||||
|
||||
def module_repr(self, module):
|
||||
"""Return a module's repr.
|
||||
|
||||
Used by the module type when the method does not raise
|
||||
NotImplementedError.
|
||||
|
||||
This method is deprecated.
|
||||
|
||||
"""
|
||||
warnings.warn("importlib.abc.Loader.module_repr() is deprecated and "
|
||||
"slated for removal in Python 3.12", DeprecationWarning)
|
||||
# The exception will cause ModuleType.__repr__ to ignore this method.
|
||||
raise NotImplementedError
|
||||
|
||||
454
Lib/importlib/_bootstrap.py
vendored
454
Lib/importlib/_bootstrap.py
vendored
@@ -51,17 +51,178 @@ def _new_module(name):
|
||||
|
||||
# Module-level locking ########################################################
|
||||
|
||||
# A dict mapping module names to weakrefs of _ModuleLock instances
|
||||
# Dictionary protected by the global import lock
|
||||
# For a list that can have a weakref to it.
|
||||
class _List(list):
|
||||
pass
|
||||
|
||||
|
||||
# Copied from weakref.py with some simplifications and modifications unique to
|
||||
# bootstrapping importlib. Many methods were simply deleting for simplicity, so if they
|
||||
# are needed in the future they may work if simply copied back in.
|
||||
class _WeakValueDictionary:
|
||||
|
||||
def __init__(self):
|
||||
self_weakref = _weakref.ref(self)
|
||||
|
||||
# Inlined to avoid issues with inheriting from _weakref.ref before _weakref is
|
||||
# set by _setup(). Since there's only one instance of this class, this is
|
||||
# not expensive.
|
||||
class KeyedRef(_weakref.ref):
|
||||
|
||||
__slots__ = "key",
|
||||
|
||||
def __new__(type, ob, key):
|
||||
self = super().__new__(type, ob, type.remove)
|
||||
self.key = key
|
||||
return self
|
||||
|
||||
def __init__(self, ob, key):
|
||||
super().__init__(ob, self.remove)
|
||||
|
||||
@staticmethod
|
||||
def remove(wr):
|
||||
nonlocal self_weakref
|
||||
|
||||
self = self_weakref()
|
||||
if self is not None:
|
||||
if self._iterating:
|
||||
self._pending_removals.append(wr.key)
|
||||
else:
|
||||
_weakref._remove_dead_weakref(self.data, wr.key)
|
||||
|
||||
self._KeyedRef = KeyedRef
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
self._pending_removals = []
|
||||
self._iterating = set()
|
||||
self.data = {}
|
||||
|
||||
def _commit_removals(self):
|
||||
pop = self._pending_removals.pop
|
||||
d = self.data
|
||||
while True:
|
||||
try:
|
||||
key = pop()
|
||||
except IndexError:
|
||||
return
|
||||
_weakref._remove_dead_weakref(d, key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
try:
|
||||
wr = self.data[key]
|
||||
except KeyError:
|
||||
return default
|
||||
else:
|
||||
if (o := wr()) is None:
|
||||
return default
|
||||
else:
|
||||
return o
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
try:
|
||||
o = self.data[key]()
|
||||
except KeyError:
|
||||
o = None
|
||||
if o is None:
|
||||
if self._pending_removals:
|
||||
self._commit_removals()
|
||||
self.data[key] = self._KeyedRef(default, key)
|
||||
return default
|
||||
else:
|
||||
return o
|
||||
|
||||
|
||||
# A dict mapping module names to weakrefs of _ModuleLock instances.
|
||||
# Dictionary protected by the global import lock.
|
||||
_module_locks = {}
|
||||
# A dict mapping thread ids to _ModuleLock instances
|
||||
_blocking_on = {}
|
||||
|
||||
# A dict mapping thread IDs to weakref'ed lists of _ModuleLock instances.
|
||||
# This maps a thread to the module locks it is blocking on acquiring. The
|
||||
# values are lists because a single thread could perform a re-entrant import
|
||||
# and be "in the process" of blocking on locks for more than one module. A
|
||||
# thread can be "in the process" because a thread cannot actually block on
|
||||
# acquiring more than one lock but it can have set up bookkeeping that reflects
|
||||
# that it intends to block on acquiring more than one lock.
|
||||
#
|
||||
# The dictionary uses a WeakValueDictionary to avoid keeping unnecessary
|
||||
# lists around, regardless of GC runs. This way there's no memory leak if
|
||||
# the list is no longer needed (GH-106176).
|
||||
_blocking_on = None
|
||||
|
||||
|
||||
class _BlockingOnManager:
|
||||
"""A context manager responsible to updating ``_blocking_on``."""
|
||||
def __init__(self, thread_id, lock):
|
||||
self.thread_id = thread_id
|
||||
self.lock = lock
|
||||
|
||||
def __enter__(self):
|
||||
"""Mark the running thread as waiting for self.lock. via _blocking_on."""
|
||||
# Interactions with _blocking_on are *not* protected by the global
|
||||
# import lock here because each thread only touches the state that it
|
||||
# owns (state keyed on its thread id). The global import lock is
|
||||
# re-entrant (i.e., a single thread may take it more than once) so it
|
||||
# wouldn't help us be correct in the face of re-entrancy either.
|
||||
|
||||
self.blocked_on = _blocking_on.setdefault(self.thread_id, _List())
|
||||
self.blocked_on.append(self.lock)
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
"""Remove self.lock from this thread's _blocking_on list."""
|
||||
self.blocked_on.remove(self.lock)
|
||||
|
||||
|
||||
class _DeadlockError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def _has_deadlocked(target_id, *, seen_ids, candidate_ids, blocking_on):
|
||||
"""Check if 'target_id' is holding the same lock as another thread(s).
|
||||
|
||||
The search within 'blocking_on' starts with the threads listed in
|
||||
'candidate_ids'. 'seen_ids' contains any threads that are considered
|
||||
already traversed in the search.
|
||||
|
||||
Keyword arguments:
|
||||
target_id -- The thread id to try to reach.
|
||||
seen_ids -- A set of threads that have already been visited.
|
||||
candidate_ids -- The thread ids from which to begin.
|
||||
blocking_on -- A dict representing the thread/blocking-on graph. This may
|
||||
be the same object as the global '_blocking_on' but it is
|
||||
a parameter to reduce the impact that global mutable
|
||||
state has on the result of this function.
|
||||
"""
|
||||
if target_id in candidate_ids:
|
||||
# If we have already reached the target_id, we're done - signal that it
|
||||
# is reachable.
|
||||
return True
|
||||
|
||||
# Otherwise, try to reach the target_id from each of the given candidate_ids.
|
||||
for tid in candidate_ids:
|
||||
if not (candidate_blocking_on := blocking_on.get(tid)):
|
||||
# There are no edges out from this node, skip it.
|
||||
continue
|
||||
elif tid in seen_ids:
|
||||
# bpo 38091: the chain of tid's we encounter here eventually leads
|
||||
# to a fixed point or a cycle, but does not reach target_id.
|
||||
# This means we would not actually deadlock. This can happen if
|
||||
# other threads are at the beginning of acquire() below.
|
||||
return False
|
||||
seen_ids.add(tid)
|
||||
|
||||
# Follow the edges out from this thread.
|
||||
edges = [lock.owner for lock in candidate_blocking_on]
|
||||
if _has_deadlocked(target_id, seen_ids=seen_ids, candidate_ids=edges,
|
||||
blocking_on=blocking_on):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class _ModuleLock:
|
||||
"""A recursive lock implementation which is able to detect deadlocks
|
||||
(e.g. thread 1 trying to take locks A then B, and thread 2 trying to
|
||||
@@ -69,33 +230,76 @@ class _ModuleLock:
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.lock = _thread.allocate_lock()
|
||||
# Create an RLock for protecting the import process for the
|
||||
# corresponding module. Since it is an RLock, a single thread will be
|
||||
# able to take it more than once. This is necessary to support
|
||||
# re-entrancy in the import system that arises from (at least) signal
|
||||
# handlers and the garbage collector. Consider the case of:
|
||||
#
|
||||
# import foo
|
||||
# -> ...
|
||||
# -> importlib._bootstrap._ModuleLock.acquire
|
||||
# -> ...
|
||||
# -> <garbage collector>
|
||||
# -> __del__
|
||||
# -> import foo
|
||||
# -> ...
|
||||
# -> importlib._bootstrap._ModuleLock.acquire
|
||||
# -> _BlockingOnManager.__enter__
|
||||
#
|
||||
# If a different thread than the running one holds the lock then the
|
||||
# thread will have to block on taking the lock, which is what we want
|
||||
# for thread safety.
|
||||
self.lock = _thread.RLock()
|
||||
self.wakeup = _thread.allocate_lock()
|
||||
|
||||
# The name of the module for which this is a lock.
|
||||
self.name = name
|
||||
|
||||
# Can end up being set to None if this lock is not owned by any thread
|
||||
# or the thread identifier for the owning thread.
|
||||
self.owner = None
|
||||
self.count = 0
|
||||
self.waiters = 0
|
||||
|
||||
# Represent the number of times the owning thread has acquired this lock
|
||||
# via a list of True. This supports RLock-like ("re-entrant lock")
|
||||
# behavior, necessary in case a single thread is following a circular
|
||||
# import dependency and needs to take the lock for a single module
|
||||
# more than once.
|
||||
#
|
||||
# Counts are represented as a list of True because list.append(True)
|
||||
# and list.pop() are both atomic and thread-safe in CPython and it's hard
|
||||
# to find another primitive with the same properties.
|
||||
self.count = []
|
||||
|
||||
# This is a count of the number of threads that are blocking on
|
||||
# self.wakeup.acquire() awaiting to get their turn holding this module
|
||||
# lock. When the module lock is released, if this is greater than
|
||||
# zero, it is decremented and `self.wakeup` is released one time. The
|
||||
# intent is that this will let one other thread make more progress on
|
||||
# acquiring this module lock. This repeats until all the threads have
|
||||
# gotten a turn.
|
||||
#
|
||||
# This is incremented in self.acquire() when a thread notices it is
|
||||
# going to have to wait for another thread to finish.
|
||||
#
|
||||
# See the comment above count for explanation of the representation.
|
||||
self.waiters = []
|
||||
|
||||
def has_deadlock(self):
|
||||
# Deadlock avoidance for concurrent circular imports.
|
||||
me = _thread.get_ident()
|
||||
tid = self.owner
|
||||
seen = set()
|
||||
while True:
|
||||
lock = _blocking_on.get(tid)
|
||||
if lock is None:
|
||||
return False
|
||||
tid = lock.owner
|
||||
if tid == me:
|
||||
return True
|
||||
if tid in seen:
|
||||
# bpo 38091: the chain of tid's we encounter here
|
||||
# eventually leads to a fixpoint or a cycle, but
|
||||
# does not reach 'me'. This means we would not
|
||||
# actually deadlock. This can happen if other
|
||||
# threads are at the beginning of acquire() below.
|
||||
return False
|
||||
seen.add(tid)
|
||||
# To avoid deadlocks for concurrent or re-entrant circular imports,
|
||||
# look at _blocking_on to see if any threads are blocking
|
||||
# on getting the import lock for any module for which the import lock
|
||||
# is held by this thread.
|
||||
return _has_deadlocked(
|
||||
# Try to find this thread.
|
||||
target_id=_thread.get_ident(),
|
||||
seen_ids=set(),
|
||||
# Start from the thread that holds the import lock for this
|
||||
# module.
|
||||
candidate_ids=[self.owner],
|
||||
# Use the global "blocking on" state.
|
||||
blocking_on=_blocking_on,
|
||||
)
|
||||
|
||||
def acquire(self):
|
||||
"""
|
||||
@@ -104,39 +308,82 @@ class _ModuleLock:
|
||||
Otherwise, the lock is always acquired and True is returned.
|
||||
"""
|
||||
tid = _thread.get_ident()
|
||||
_blocking_on[tid] = self
|
||||
try:
|
||||
with _BlockingOnManager(tid, self):
|
||||
while True:
|
||||
# Protect interaction with state on self with a per-module
|
||||
# lock. This makes it safe for more than one thread to try to
|
||||
# acquire the lock for a single module at the same time.
|
||||
with self.lock:
|
||||
if self.count == 0 or self.owner == tid:
|
||||
if self.count == [] or self.owner == tid:
|
||||
# If the lock for this module is unowned then we can
|
||||
# take the lock immediately and succeed. If the lock
|
||||
# for this module is owned by the running thread then
|
||||
# we can also allow the acquire to succeed. This
|
||||
# supports circular imports (thread T imports module A
|
||||
# which imports module B which imports module A).
|
||||
self.owner = tid
|
||||
self.count += 1
|
||||
self.count.append(True)
|
||||
return True
|
||||
|
||||
# At this point we know the lock is held (because count !=
|
||||
# 0) by another thread (because owner != tid). We'll have
|
||||
# to get in line to take the module lock.
|
||||
|
||||
# But first, check to see if this thread would create a
|
||||
# deadlock by acquiring this module lock. If it would
|
||||
# then just stop with an error.
|
||||
#
|
||||
# It's not clear who is expected to handle this error.
|
||||
# There is one handler in _lock_unlock_module but many
|
||||
# times this method is called when entering the context
|
||||
# manager _ModuleLockManager instead - so _DeadlockError
|
||||
# will just propagate up to application code.
|
||||
#
|
||||
# This seems to be more than just a hypothetical -
|
||||
# https://stackoverflow.com/questions/59509154
|
||||
# https://github.com/encode/django-rest-framework/issues/7078
|
||||
if self.has_deadlock():
|
||||
raise _DeadlockError('deadlock detected by %r' % self)
|
||||
raise _DeadlockError(f'deadlock detected by {self!r}')
|
||||
|
||||
# Check to see if we're going to be able to acquire the
|
||||
# lock. If we are going to have to wait then increment
|
||||
# the waiters so `self.release` will know to unblock us
|
||||
# later on. We do this part non-blockingly so we don't
|
||||
# get stuck here before we increment waiters. We have
|
||||
# this extra acquire call (in addition to the one below,
|
||||
# outside the self.lock context manager) to make sure
|
||||
# self.wakeup is held when the next acquire is called (so
|
||||
# we block). This is probably needlessly complex and we
|
||||
# should just take self.wakeup in the return codepath
|
||||
# above.
|
||||
if self.wakeup.acquire(False):
|
||||
self.waiters += 1
|
||||
# Wait for a release() call
|
||||
self.waiters.append(None)
|
||||
|
||||
# Now take the lock in a blocking fashion. This won't
|
||||
# complete until the thread holding this lock
|
||||
# (self.owner) calls self.release.
|
||||
self.wakeup.acquire()
|
||||
|
||||
# Taking the lock has served its purpose (making us wait), so we can
|
||||
# give it up now. We'll take it w/o blocking again on the
|
||||
# next iteration around this 'while' loop.
|
||||
self.wakeup.release()
|
||||
finally:
|
||||
del _blocking_on[tid]
|
||||
|
||||
def release(self):
|
||||
tid = _thread.get_ident()
|
||||
with self.lock:
|
||||
if self.owner != tid:
|
||||
raise RuntimeError('cannot release un-acquired lock')
|
||||
assert self.count > 0
|
||||
self.count -= 1
|
||||
if self.count == 0:
|
||||
assert len(self.count) > 0
|
||||
self.count.pop()
|
||||
if not len(self.count):
|
||||
self.owner = None
|
||||
if self.waiters:
|
||||
self.waiters -= 1
|
||||
if len(self.waiters) > 0:
|
||||
self.waiters.pop()
|
||||
self.wakeup.release()
|
||||
|
||||
def __repr__(self):
|
||||
return '_ModuleLock({!r}) at {}'.format(self.name, id(self))
|
||||
return f'_ModuleLock({self.name!r}) at {id(self)}'
|
||||
|
||||
|
||||
class _DummyModuleLock:
|
||||
@@ -157,7 +404,7 @@ class _DummyModuleLock:
|
||||
self.count -= 1
|
||||
|
||||
def __repr__(self):
|
||||
return '_DummyModuleLock({!r}) at {}'.format(self.name, id(self))
|
||||
return f'_DummyModuleLock({self.name!r}) at {id(self)}'
|
||||
|
||||
|
||||
class _ModuleLockManager:
|
||||
@@ -254,7 +501,7 @@ def _requires_builtin(fxn):
|
||||
"""Decorator to verify the named module is built-in."""
|
||||
def _requires_builtin_wrapper(self, fullname):
|
||||
if fullname not in sys.builtin_module_names:
|
||||
raise ImportError('{!r} is not a built-in module'.format(fullname),
|
||||
raise ImportError(f'{fullname!r} is not a built-in module',
|
||||
name=fullname)
|
||||
return fxn(self, fullname)
|
||||
_wrap(_requires_builtin_wrapper, fxn)
|
||||
@@ -265,7 +512,7 @@ def _requires_frozen(fxn):
|
||||
"""Decorator to verify the named module is frozen."""
|
||||
def _requires_frozen_wrapper(self, fullname):
|
||||
if not _imp.is_frozen(fullname):
|
||||
raise ImportError('{!r} is not a frozen module'.format(fullname),
|
||||
raise ImportError(f'{fullname!r} is not a frozen module',
|
||||
name=fullname)
|
||||
return fxn(self, fullname)
|
||||
_wrap(_requires_frozen_wrapper, fxn)
|
||||
@@ -297,11 +544,6 @@ def _module_repr(module):
|
||||
loader = getattr(module, '__loader__', None)
|
||||
if spec := getattr(module, "__spec__", None):
|
||||
return _module_repr_from_spec(spec)
|
||||
elif hasattr(loader, 'module_repr'):
|
||||
try:
|
||||
return loader.module_repr(module)
|
||||
except Exception:
|
||||
pass
|
||||
# Fall through to a catch-all which always succeeds.
|
||||
try:
|
||||
name = module.__name__
|
||||
@@ -311,11 +553,11 @@ def _module_repr(module):
|
||||
filename = module.__file__
|
||||
except AttributeError:
|
||||
if loader is None:
|
||||
return '<module {!r}>'.format(name)
|
||||
return f'<module {name!r}>'
|
||||
else:
|
||||
return '<module {!r} ({!r})>'.format(name, loader)
|
||||
return f'<module {name!r} ({loader!r})>'
|
||||
else:
|
||||
return '<module {!r} from {!r}>'.format(name, filename)
|
||||
return f'<module {name!r} from {filename!r}>'
|
||||
|
||||
|
||||
class ModuleSpec:
|
||||
@@ -369,14 +611,12 @@ class ModuleSpec:
|
||||
self._cached = None
|
||||
|
||||
def __repr__(self):
|
||||
args = ['name={!r}'.format(self.name),
|
||||
'loader={!r}'.format(self.loader)]
|
||||
args = [f'name={self.name!r}', f'loader={self.loader!r}']
|
||||
if self.origin is not None:
|
||||
args.append('origin={!r}'.format(self.origin))
|
||||
args.append(f'origin={self.origin!r}')
|
||||
if self.submodule_search_locations is not None:
|
||||
args.append('submodule_search_locations={}'
|
||||
.format(self.submodule_search_locations))
|
||||
return '{}({})'.format(self.__class__.__name__, ', '.join(args))
|
||||
args.append(f'submodule_search_locations={self.submodule_search_locations}')
|
||||
return f'{self.__class__.__name__}({", ".join(args)})'
|
||||
|
||||
def __eq__(self, other):
|
||||
smsl = self.submodule_search_locations
|
||||
@@ -583,18 +823,17 @@ def module_from_spec(spec):
|
||||
|
||||
def _module_repr_from_spec(spec):
|
||||
"""Return the repr to use for the module."""
|
||||
# We mostly replicate _module_repr() using the spec attributes.
|
||||
name = '?' if spec.name is None else spec.name
|
||||
if spec.origin is None:
|
||||
if spec.loader is None:
|
||||
return '<module {!r}>'.format(name)
|
||||
return f'<module {name!r}>'
|
||||
else:
|
||||
return '<module {!r} ({!r})>'.format(name, spec.loader)
|
||||
return f'<module {name!r} (namespace) from {list(spec.loader._path)}>'
|
||||
else:
|
||||
if spec.has_location:
|
||||
return '<module {!r} from {!r}>'.format(name, spec.origin)
|
||||
return f'<module {name!r} from {spec.origin!r}>'
|
||||
else:
|
||||
return '<module {!r} ({})>'.format(spec.name, spec.origin)
|
||||
return f'<module {spec.name!r} ({spec.origin})>'
|
||||
|
||||
|
||||
# Used by importlib.reload() and _load_module_shim().
|
||||
@@ -603,7 +842,7 @@ def _exec(spec, module):
|
||||
name = spec.name
|
||||
with _ModuleLockManager(name):
|
||||
if sys.modules.get(name) is not module:
|
||||
msg = 'module {!r} not in sys.modules'.format(name)
|
||||
msg = f'module {name!r} not in sys.modules'
|
||||
raise ImportError(msg, name=name)
|
||||
try:
|
||||
if spec.loader is None:
|
||||
@@ -735,46 +974,18 @@ class BuiltinImporter:
|
||||
|
||||
_ORIGIN = "built-in"
|
||||
|
||||
@staticmethod
|
||||
def module_repr(module):
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
_warnings.warn("BuiltinImporter.module_repr() is deprecated and "
|
||||
"slated for removal in Python 3.12", DeprecationWarning)
|
||||
return f'<module {module.__name__!r} ({BuiltinImporter._ORIGIN})>'
|
||||
|
||||
@classmethod
|
||||
def find_spec(cls, fullname, path=None, target=None):
|
||||
if path is not None:
|
||||
return None
|
||||
if _imp.is_builtin(fullname):
|
||||
return spec_from_loader(fullname, cls, origin=cls._ORIGIN)
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_module(cls, fullname, path=None):
|
||||
"""Find the built-in module.
|
||||
|
||||
If 'path' is ever specified then the search is considered a failure.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
_warnings.warn("BuiltinImporter.find_module() is deprecated and "
|
||||
"slated for removal in Python 3.12; use find_spec() instead",
|
||||
DeprecationWarning)
|
||||
spec = cls.find_spec(fullname, path)
|
||||
return spec.loader if spec is not None else None
|
||||
|
||||
@staticmethod
|
||||
def create_module(spec):
|
||||
"""Create a built-in module"""
|
||||
if spec.name not in sys.builtin_module_names:
|
||||
raise ImportError('{!r} is not a built-in module'.format(spec.name),
|
||||
raise ImportError(f'{spec.name!r} is not a built-in module',
|
||||
name=spec.name)
|
||||
return _call_with_frames_removed(_imp.create_builtin, spec)
|
||||
|
||||
@@ -815,17 +1026,6 @@ class FrozenImporter:
|
||||
|
||||
_ORIGIN = "frozen"
|
||||
|
||||
@staticmethod
|
||||
def module_repr(m):
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
_warnings.warn("FrozenImporter.module_repr() is deprecated and "
|
||||
"slated for removal in Python 3.12", DeprecationWarning)
|
||||
return '<module {!r} ({})>'.format(m.__name__, FrozenImporter._ORIGIN)
|
||||
|
||||
@classmethod
|
||||
def _fix_up_module(cls, module):
|
||||
spec = module.__spec__
|
||||
@@ -950,18 +1150,6 @@ class FrozenImporter:
|
||||
spec.submodule_search_locations.insert(0, pkgdir)
|
||||
return spec
|
||||
|
||||
@classmethod
|
||||
def find_module(cls, fullname, path=None):
|
||||
"""Find a frozen module.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
_warnings.warn("FrozenImporter.find_module() is deprecated and "
|
||||
"slated for removal in Python 3.12; use find_spec() instead",
|
||||
DeprecationWarning)
|
||||
return cls if _imp.is_frozen(fullname) else None
|
||||
|
||||
@staticmethod
|
||||
def create_module(spec):
|
||||
"""Set __file__, if able."""
|
||||
@@ -1041,17 +1229,7 @@ def _resolve_name(name, package, level):
|
||||
if len(bits) < level:
|
||||
raise ImportError('attempted relative import beyond top-level package')
|
||||
base = bits[0]
|
||||
return '{}.{}'.format(base, name) if name else base
|
||||
|
||||
|
||||
def _find_spec_legacy(finder, name, path):
|
||||
msg = (f"{_object_name(finder)}.find_spec() not found; "
|
||||
"falling back to find_module()")
|
||||
_warnings.warn(msg, ImportWarning)
|
||||
loader = finder.find_module(name, path)
|
||||
if loader is None:
|
||||
return None
|
||||
return spec_from_loader(name, loader)
|
||||
return f'{base}.{name}' if name else base
|
||||
|
||||
|
||||
def _find_spec(name, path, target=None):
|
||||
@@ -1074,9 +1252,7 @@ def _find_spec(name, path, target=None):
|
||||
try:
|
||||
find_spec = finder.find_spec
|
||||
except AttributeError:
|
||||
spec = _find_spec_legacy(finder, name, path)
|
||||
if spec is None:
|
||||
continue
|
||||
continue
|
||||
else:
|
||||
spec = find_spec(name, path, target)
|
||||
if spec is not None:
|
||||
@@ -1104,7 +1280,7 @@ def _find_spec(name, path, target=None):
|
||||
def _sanity_check(name, package, level):
|
||||
"""Verify arguments are "sane"."""
|
||||
if not isinstance(name, str):
|
||||
raise TypeError('module name must be str, not {}'.format(type(name)))
|
||||
raise TypeError(f'module name must be str, not {type(name)}')
|
||||
if level < 0:
|
||||
raise ValueError('level must be >= 0')
|
||||
if level > 0:
|
||||
@@ -1134,13 +1310,13 @@ def _find_and_load_unlocked(name, import_):
|
||||
try:
|
||||
path = parent_module.__path__
|
||||
except AttributeError:
|
||||
msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent)
|
||||
msg = f'{_ERR_MSG_PREFIX}{name!r}; {parent!r} is not a package'
|
||||
raise ModuleNotFoundError(msg, name=name) from None
|
||||
parent_spec = parent_module.__spec__
|
||||
child = name.rpartition('.')[2]
|
||||
spec = _find_spec(name, path)
|
||||
if spec is None:
|
||||
raise ModuleNotFoundError(_ERR_MSG.format(name), name=name)
|
||||
raise ModuleNotFoundError(f'{_ERR_MSG_PREFIX}{name!r}', name=name)
|
||||
else:
|
||||
if parent_spec:
|
||||
# Temporarily add child we are currently importing to parent's
|
||||
@@ -1185,8 +1361,7 @@ def _find_and_load(name, import_):
|
||||
_lock_unlock_module(name)
|
||||
|
||||
if module is None:
|
||||
message = ('import of {} halted; '
|
||||
'None in sys.modules'.format(name))
|
||||
message = f'import of {name} halted; None in sys.modules'
|
||||
raise ModuleNotFoundError(message, name=name)
|
||||
|
||||
return module
|
||||
@@ -1230,7 +1405,7 @@ def _handle_fromlist(module, fromlist, import_, *, recursive=False):
|
||||
_handle_fromlist(module, module.__all__, import_,
|
||||
recursive=True)
|
||||
elif not hasattr(module, x):
|
||||
from_name = '{}.{}'.format(module.__name__, x)
|
||||
from_name = f'{module.__name__}.{x}'
|
||||
try:
|
||||
_call_with_frames_removed(import_, from_name)
|
||||
except ModuleNotFoundError as exc:
|
||||
@@ -1257,7 +1432,7 @@ def _calc___package__(globals):
|
||||
if spec is not None and package != spec.parent:
|
||||
_warnings.warn("__package__ != __spec__.parent "
|
||||
f"({package!r} != {spec.parent!r})",
|
||||
ImportWarning, stacklevel=3)
|
||||
DeprecationWarning, stacklevel=3)
|
||||
return package
|
||||
elif spec is not None:
|
||||
return spec.parent
|
||||
@@ -1323,7 +1498,7 @@ def _setup(sys_module, _imp_module):
|
||||
modules, those two modules must be explicitly passed in.
|
||||
|
||||
"""
|
||||
global _imp, sys
|
||||
global _imp, sys, _blocking_on
|
||||
_imp = _imp_module
|
||||
sys = sys_module
|
||||
|
||||
@@ -1351,6 +1526,9 @@ def _setup(sys_module, _imp_module):
|
||||
builtin_module = sys.modules[builtin_name]
|
||||
setattr(self_module, builtin_name, builtin_module)
|
||||
|
||||
# Instantiation requires _weakref to have been set.
|
||||
_blocking_on = _WeakValueDictionary()
|
||||
|
||||
|
||||
def _install(sys_module, _imp_module):
|
||||
"""Install importers for builtin and frozen modules"""
|
||||
|
||||
247
Lib/importlib/_bootstrap_external.py
vendored
247
Lib/importlib/_bootstrap_external.py
vendored
@@ -182,12 +182,22 @@ else:
|
||||
return path.startswith(path_separators)
|
||||
|
||||
|
||||
def _path_abspath(path):
|
||||
"""Replacement for os.path.abspath."""
|
||||
if not _path_isabs(path):
|
||||
for sep in path_separators:
|
||||
path = path.removeprefix(f".{sep}")
|
||||
return _path_join(_os.getcwd(), path)
|
||||
else:
|
||||
return path
|
||||
|
||||
|
||||
def _write_atomic(path, data, mode=0o666):
|
||||
"""Best-effort function to write data to a path atomically.
|
||||
Be prepared to handle a FileExistsError if concurrent writing of the
|
||||
temporary file is attempted."""
|
||||
# id() is used to generate a pseudo-random filename.
|
||||
path_tmp = '{}.{}'.format(path, id(path))
|
||||
path_tmp = f'{path}.{id(path)}'
|
||||
fd = _os.open(path_tmp,
|
||||
_os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, mode & 0o666)
|
||||
try:
|
||||
@@ -403,11 +413,45 @@ _code_type = type(_write_atomic.__code__)
|
||||
# Python 3.11a7 3492 (make POP_JUMP_IF_NONE/NOT_NONE/TRUE/FALSE relative)
|
||||
# Python 3.11a7 3493 (Make JUMP_IF_TRUE_OR_POP/JUMP_IF_FALSE_OR_POP relative)
|
||||
# Python 3.11a7 3494 (New location info table)
|
||||
# Python 3.11b4 3495 (Set line number of module's RESUME instr to 0 per PEP 626)
|
||||
# Python 3.12 will start with magic number 3500
|
||||
# Python 3.12a1 3500 (Remove PRECALL opcode)
|
||||
# Python 3.12a1 3501 (YIELD_VALUE oparg == stack_depth)
|
||||
# Python 3.12a1 3502 (LOAD_FAST_CHECK, no NULL-check in LOAD_FAST)
|
||||
# Python 3.12a1 3503 (Shrink LOAD_METHOD cache)
|
||||
# Python 3.12a1 3504 (Merge LOAD_METHOD back into LOAD_ATTR)
|
||||
# Python 3.12a1 3505 (Specialization/Cache for FOR_ITER)
|
||||
# Python 3.12a1 3506 (Add BINARY_SLICE and STORE_SLICE instructions)
|
||||
# Python 3.12a1 3507 (Set lineno of module's RESUME to 0)
|
||||
# Python 3.12a1 3508 (Add CLEANUP_THROW)
|
||||
# Python 3.12a1 3509 (Conditional jumps only jump forward)
|
||||
# Python 3.12a2 3510 (FOR_ITER leaves iterator on the stack)
|
||||
# Python 3.12a2 3511 (Add STOPITERATION_ERROR instruction)
|
||||
# Python 3.12a2 3512 (Remove all unused consts from code objects)
|
||||
# Python 3.12a4 3513 (Add CALL_INTRINSIC_1 instruction, removed STOPITERATION_ERROR, PRINT_EXPR, IMPORT_STAR)
|
||||
# Python 3.12a4 3514 (Remove ASYNC_GEN_WRAP, LIST_TO_TUPLE, and UNARY_POSITIVE)
|
||||
# Python 3.12a5 3515 (Embed jump mask in COMPARE_OP oparg)
|
||||
# Python 3.12a5 3516 (Add COMPARE_AND_BRANCH instruction)
|
||||
# Python 3.12a5 3517 (Change YIELD_VALUE oparg to exception block depth)
|
||||
# Python 3.12a6 3518 (Add RETURN_CONST instruction)
|
||||
# Python 3.12a6 3519 (Modify SEND instruction)
|
||||
# Python 3.12a6 3520 (Remove PREP_RERAISE_STAR, add CALL_INTRINSIC_2)
|
||||
# Python 3.12a7 3521 (Shrink the LOAD_GLOBAL caches)
|
||||
# Python 3.12a7 3522 (Removed JUMP_IF_FALSE_OR_POP/JUMP_IF_TRUE_OR_POP)
|
||||
# Python 3.12a7 3523 (Convert COMPARE_AND_BRANCH back to COMPARE_OP)
|
||||
# Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches)
|
||||
# Python 3.12b1 3525 (Shrink the CALL caches)
|
||||
# Python 3.12b1 3526 (Add instrumentation support)
|
||||
# Python 3.12b1 3527 (Add LOAD_SUPER_ATTR)
|
||||
# Python 3.12b1 3528 (Add LOAD_SUPER_ATTR_METHOD specialization)
|
||||
# Python 3.12b1 3529 (Inline list/dict/set comprehensions)
|
||||
# Python 3.12b1 3530 (Shrink the LOAD_SUPER_ATTR caches)
|
||||
# Python 3.12b1 3531 (Add PEP 695 changes)
|
||||
|
||||
# Python 3.13 will start with 3550
|
||||
|
||||
# Please don't copy-paste the same pre-release tag for new entries above!!!
|
||||
# You should always use the *upcoming* tag. For example, if 3.12a6 came out
|
||||
# a week ago, I should put "Python 3.12a7" next to my new magic number.
|
||||
|
||||
#
|
||||
# MAGIC must change whenever the bytecode emitted by the compiler may no
|
||||
# longer be understood by older implementations of the eval loop (usually
|
||||
# due to the addition of new opcodes).
|
||||
@@ -417,7 +461,7 @@ _code_type = type(_write_atomic.__code__)
|
||||
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
|
||||
# in PC/launcher.c must also be updated.
|
||||
|
||||
MAGIC_NUMBER = (3495).to_bytes(2, 'little') + b'\r\n'
|
||||
MAGIC_NUMBER = (3531).to_bytes(2, 'little') + b'\r\n'
|
||||
|
||||
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
|
||||
|
||||
@@ -474,8 +518,8 @@ def cache_from_source(path, debug_override=None, *, optimization=None):
|
||||
optimization = str(optimization)
|
||||
if optimization != '':
|
||||
if not optimization.isalnum():
|
||||
raise ValueError('{!r} is not alphanumeric'.format(optimization))
|
||||
almost_filename = '{}.{}{}'.format(almost_filename, _OPT, optimization)
|
||||
raise ValueError(f'{optimization!r} is not alphanumeric')
|
||||
almost_filename = f'{almost_filename}.{_OPT}{optimization}'
|
||||
filename = almost_filename + BYTECODE_SUFFIXES[0]
|
||||
if sys.pycache_prefix is not None:
|
||||
# We need an absolute path to the py file to avoid the possibility of
|
||||
@@ -486,8 +530,7 @@ def cache_from_source(path, debug_override=None, *, optimization=None):
|
||||
# make it absolute (`C:\Somewhere\Foo\Bar`), then make it root-relative
|
||||
# (`Somewhere\Foo\Bar`), so we end up placing the bytecode file in an
|
||||
# unambiguous `C:\Bytecode\Somewhere\Foo\Bar\`.
|
||||
if not _path_isabs(head):
|
||||
head = _path_join(_os.getcwd(), head)
|
||||
head = _path_abspath(head)
|
||||
|
||||
# Strip initial drive from a Windows path. We know we have an absolute
|
||||
# path here, so the second part of the check rules out a POSIX path that
|
||||
@@ -619,26 +662,6 @@ def _check_name(method):
|
||||
return _check_name_wrapper
|
||||
|
||||
|
||||
def _find_module_shim(self, fullname):
|
||||
"""Try to find a loader for the specified module by delegating to
|
||||
self.find_loader().
|
||||
|
||||
This method is deprecated in favor of finder.find_spec().
|
||||
|
||||
"""
|
||||
_warnings.warn("find_module() is deprecated and "
|
||||
"slated for removal in Python 3.12; use find_spec() instead",
|
||||
DeprecationWarning)
|
||||
# Call find_loader(). If it returns a string (indicating this
|
||||
# is a namespace package portion), generate a warning and
|
||||
# return None.
|
||||
loader, portions = self.find_loader(fullname)
|
||||
if loader is None and len(portions):
|
||||
msg = 'Not importing directory {}: missing __init__'
|
||||
_warnings.warn(msg.format(portions[0]), ImportWarning)
|
||||
return loader
|
||||
|
||||
|
||||
def _classify_pyc(data, name, exc_details):
|
||||
"""Perform basic validity checking of a pyc header and return the flags field,
|
||||
which determines how the pyc should be further validated against the source.
|
||||
@@ -733,7 +756,7 @@ def _compile_bytecode(data, name=None, bytecode_path=None, source_path=None):
|
||||
_imp._fix_co_filename(code, source_path)
|
||||
return code
|
||||
else:
|
||||
raise ImportError('Non-code object in {!r}'.format(bytecode_path),
|
||||
raise ImportError(f'Non-code object in {bytecode_path!r}',
|
||||
name=name, path=bytecode_path)
|
||||
|
||||
|
||||
@@ -800,11 +823,10 @@ def spec_from_file_location(name, location=None, *, loader=None,
|
||||
pass
|
||||
else:
|
||||
location = _os.fspath(location)
|
||||
if not _path_isabs(location):
|
||||
try:
|
||||
location = _path_join(_os.getcwd(), location)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
location = _path_abspath(location)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# If the location is on the filesystem, but doesn't actually exist,
|
||||
# we could return None here, indicating that the location is not
|
||||
@@ -846,6 +868,54 @@ def spec_from_file_location(name, location=None, *, loader=None,
|
||||
return spec
|
||||
|
||||
|
||||
def _bless_my_loader(module_globals):
|
||||
"""Helper function for _warnings.c
|
||||
|
||||
See GH#97850 for details.
|
||||
"""
|
||||
# 2022-10-06(warsaw): For now, this helper is only used in _warnings.c and
|
||||
# that use case only has the module globals. This function could be
|
||||
# extended to accept either that or a module object. However, in the
|
||||
# latter case, it would be better to raise certain exceptions when looking
|
||||
# at a module, which should have either a __loader__ or __spec__.loader.
|
||||
# For backward compatibility, it is possible that we'll get an empty
|
||||
# dictionary for the module globals, and that cannot raise an exception.
|
||||
if not isinstance(module_globals, dict):
|
||||
return None
|
||||
|
||||
missing = object()
|
||||
loader = module_globals.get('__loader__', None)
|
||||
spec = module_globals.get('__spec__', missing)
|
||||
|
||||
if loader is None:
|
||||
if spec is missing:
|
||||
# If working with a module:
|
||||
# raise AttributeError('Module globals is missing a __spec__')
|
||||
return None
|
||||
elif spec is None:
|
||||
raise ValueError('Module globals is missing a __spec__.loader')
|
||||
|
||||
spec_loader = getattr(spec, 'loader', missing)
|
||||
|
||||
if spec_loader in (missing, None):
|
||||
if loader is None:
|
||||
exc = AttributeError if spec_loader is missing else ValueError
|
||||
raise exc('Module globals is missing a __spec__.loader')
|
||||
_warnings.warn(
|
||||
'Module globals is missing a __spec__.loader',
|
||||
DeprecationWarning)
|
||||
spec_loader = loader
|
||||
|
||||
assert spec_loader is not None
|
||||
if loader is not None and loader != spec_loader:
|
||||
_warnings.warn(
|
||||
'Module globals; __loader__ != __spec__.loader',
|
||||
DeprecationWarning)
|
||||
return loader
|
||||
|
||||
return spec_loader
|
||||
|
||||
|
||||
# Loaders #####################################################################
|
||||
|
||||
class WindowsRegistryFinder:
|
||||
@@ -898,22 +968,6 @@ class WindowsRegistryFinder:
|
||||
origin=filepath)
|
||||
return spec
|
||||
|
||||
@classmethod
|
||||
def find_module(cls, fullname, path=None):
|
||||
"""Find module named in the registry.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
_warnings.warn("WindowsRegistryFinder.find_module() is deprecated and "
|
||||
"slated for removal in Python 3.12; use find_spec() instead",
|
||||
DeprecationWarning)
|
||||
spec = cls.find_spec(fullname, path)
|
||||
if spec is not None:
|
||||
return spec.loader
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class _LoaderBasics:
|
||||
|
||||
@@ -935,8 +989,8 @@ class _LoaderBasics:
|
||||
"""Execute the module."""
|
||||
code = self.get_code(module.__name__)
|
||||
if code is None:
|
||||
raise ImportError('cannot load module {!r} when get_code() '
|
||||
'returns None'.format(module.__name__))
|
||||
raise ImportError(f'cannot load module {module.__name__!r} when '
|
||||
'get_code() returns None')
|
||||
_bootstrap._call_with_frames_removed(exec, code, module.__dict__)
|
||||
|
||||
def load_module(self, fullname):
|
||||
@@ -1077,7 +1131,8 @@ class SourceLoader(_LoaderBasics):
|
||||
source_mtime is not None):
|
||||
if hash_based:
|
||||
if source_hash is None:
|
||||
source_hash = _imp.source_hash(source_bytes)
|
||||
source_hash = _imp.source_hash(_RAW_MAGIC_NUMBER,
|
||||
source_bytes)
|
||||
data = _code_to_hash_pyc(code_object, source_hash, check_source)
|
||||
else:
|
||||
data = _code_to_timestamp_pyc(code_object, source_mtime,
|
||||
@@ -1321,7 +1376,7 @@ class _NamespacePath:
|
||||
return len(self._recalculate())
|
||||
|
||||
def __repr__(self):
|
||||
return '_NamespacePath({!r})'.format(self._path)
|
||||
return f'_NamespacePath({self._path!r})'
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._recalculate()
|
||||
@@ -1332,22 +1387,11 @@ class _NamespacePath:
|
||||
|
||||
# This class is actually exposed publicly in a namespace package's __loader__
|
||||
# attribute, so it should be available through a non-private name.
|
||||
# https://bugs.python.org/issue35673
|
||||
# https://github.com/python/cpython/issues/92054
|
||||
class NamespaceLoader:
|
||||
def __init__(self, name, path, path_finder):
|
||||
self._path = _NamespacePath(name, path, path_finder)
|
||||
|
||||
@staticmethod
|
||||
def module_repr(module):
|
||||
"""Return repr for the module.
|
||||
|
||||
The method is deprecated. The import machinery does the job itself.
|
||||
|
||||
"""
|
||||
_warnings.warn("NamespaceLoader.module_repr() is deprecated and "
|
||||
"slated for removal in Python 3.12", DeprecationWarning)
|
||||
return '<module {!r} (namespace)>'.format(module.__name__)
|
||||
|
||||
def is_package(self, fullname):
|
||||
return True
|
||||
|
||||
@@ -1440,27 +1484,6 @@ class PathFinder:
|
||||
sys.path_importer_cache[path] = finder
|
||||
return finder
|
||||
|
||||
@classmethod
|
||||
def _legacy_get_spec(cls, fullname, finder):
|
||||
# This would be a good place for a DeprecationWarning if
|
||||
# we ended up going that route.
|
||||
if hasattr(finder, 'find_loader'):
|
||||
msg = (f"{_bootstrap._object_name(finder)}.find_spec() not found; "
|
||||
"falling back to find_loader()")
|
||||
_warnings.warn(msg, ImportWarning)
|
||||
loader, portions = finder.find_loader(fullname)
|
||||
else:
|
||||
msg = (f"{_bootstrap._object_name(finder)}.find_spec() not found; "
|
||||
"falling back to find_module()")
|
||||
_warnings.warn(msg, ImportWarning)
|
||||
loader = finder.find_module(fullname)
|
||||
portions = []
|
||||
if loader is not None:
|
||||
return _bootstrap.spec_from_loader(fullname, loader)
|
||||
spec = _bootstrap.ModuleSpec(fullname, None)
|
||||
spec.submodule_search_locations = portions
|
||||
return spec
|
||||
|
||||
@classmethod
|
||||
def _get_spec(cls, fullname, path, target=None):
|
||||
"""Find the loader or namespace_path for this module/package name."""
|
||||
@@ -1472,10 +1495,7 @@ class PathFinder:
|
||||
continue
|
||||
finder = cls._path_importer_cache(entry)
|
||||
if finder is not None:
|
||||
if hasattr(finder, 'find_spec'):
|
||||
spec = finder.find_spec(fullname, target)
|
||||
else:
|
||||
spec = cls._legacy_get_spec(fullname, finder)
|
||||
spec = finder.find_spec(fullname, target)
|
||||
if spec is None:
|
||||
continue
|
||||
if spec.loader is not None:
|
||||
@@ -1517,22 +1537,6 @@ class PathFinder:
|
||||
else:
|
||||
return spec
|
||||
|
||||
@classmethod
|
||||
def find_module(cls, fullname, path=None):
|
||||
"""find the module on sys.path or 'path' based on sys.path_hooks and
|
||||
sys.path_importer_cache.
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
_warnings.warn("PathFinder.find_module() is deprecated and "
|
||||
"slated for removal in Python 3.12; use find_spec() instead",
|
||||
DeprecationWarning)
|
||||
spec = cls.find_spec(fullname, path)
|
||||
if spec is None:
|
||||
return None
|
||||
return spec.loader
|
||||
|
||||
@staticmethod
|
||||
def find_distributions(*args, **kwargs):
|
||||
"""
|
||||
@@ -1567,10 +1571,8 @@ class FileFinder:
|
||||
# Base (directory) path
|
||||
if not path or path == '.':
|
||||
self.path = _os.getcwd()
|
||||
elif not _path_isabs(path):
|
||||
self.path = _path_join(_os.getcwd(), path)
|
||||
else:
|
||||
self.path = path
|
||||
self.path = _path_abspath(path)
|
||||
self._path_mtime = -1
|
||||
self._path_cache = set()
|
||||
self._relaxed_path_cache = set()
|
||||
@@ -1579,23 +1581,6 @@ class FileFinder:
|
||||
"""Invalidate the directory mtime."""
|
||||
self._path_mtime = -1
|
||||
|
||||
find_module = _find_module_shim
|
||||
|
||||
def find_loader(self, fullname):
|
||||
"""Try to find a loader for the specified module, or the namespace
|
||||
package portions. Returns (loader, list-of-portions).
|
||||
|
||||
This method is deprecated. Use find_spec() instead.
|
||||
|
||||
"""
|
||||
_warnings.warn("FileFinder.find_loader() is deprecated and "
|
||||
"slated for removal in Python 3.12; use find_spec() instead",
|
||||
DeprecationWarning)
|
||||
spec = self.find_spec(fullname)
|
||||
if spec is None:
|
||||
return None, []
|
||||
return spec.loader, spec.submodule_search_locations or []
|
||||
|
||||
def _get_spec(self, loader_class, fullname, path, smsl, target):
|
||||
loader = loader_class(fullname, path)
|
||||
return spec_from_file_location(fullname, path, loader=loader,
|
||||
@@ -1675,7 +1660,7 @@ class FileFinder:
|
||||
for item in contents:
|
||||
name, dot, suffix = item.partition('.')
|
||||
if dot:
|
||||
new_name = '{}.{}'.format(name, suffix.lower())
|
||||
new_name = f'{name}.{suffix.lower()}'
|
||||
else:
|
||||
new_name = name
|
||||
lower_suffix_contents.add(new_name)
|
||||
@@ -1702,7 +1687,7 @@ class FileFinder:
|
||||
return path_hook_for_FileFinder
|
||||
|
||||
def __repr__(self):
|
||||
return 'FileFinder({!r})'.format(self.path)
|
||||
return f'FileFinder({self.path!r})'
|
||||
|
||||
|
||||
# Import setup ###############################################################
|
||||
@@ -1720,6 +1705,8 @@ def _fix_up_module(ns, name, pathname, cpathname=None):
|
||||
loader = SourceFileLoader(name, pathname)
|
||||
if not spec:
|
||||
spec = spec_from_file_location(name, pathname, loader=loader)
|
||||
if cpathname:
|
||||
spec.cached = _path_abspath(cpathname)
|
||||
try:
|
||||
ns['__spec__'] = spec
|
||||
ns['__loader__'] = loader
|
||||
|
||||
111
Lib/importlib/abc.py
vendored
111
Lib/importlib/abc.py
vendored
@@ -15,20 +15,29 @@ from ._abc import Loader
|
||||
import abc
|
||||
import warnings
|
||||
|
||||
# for compatibility with Python 3.10
|
||||
from .resources.abc import ResourceReader, Traversable, TraversableResources
|
||||
from .resources import abc as _resources_abc
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Loader', 'Finder', 'MetaPathFinder', 'PathEntryFinder',
|
||||
'Loader', 'MetaPathFinder', 'PathEntryFinder',
|
||||
'ResourceLoader', 'InspectLoader', 'ExecutionLoader',
|
||||
'FileLoader', 'SourceLoader',
|
||||
|
||||
# for compatibility with Python 3.10
|
||||
'ResourceReader', 'Traversable', 'TraversableResources',
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
"""
|
||||
For backwards compatibility, continue to make names
|
||||
from _resources_abc available through this module. #93963
|
||||
"""
|
||||
if name in _resources_abc.__all__:
|
||||
obj = getattr(_resources_abc, name)
|
||||
warnings._deprecated(f"{__name__}.{name}", remove=(3, 14))
|
||||
globals()[name] = obj
|
||||
return obj
|
||||
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
|
||||
|
||||
|
||||
def _register(abstract_cls, *classes):
|
||||
for cls in classes:
|
||||
abstract_cls.register(cls)
|
||||
@@ -40,38 +49,6 @@ def _register(abstract_cls, *classes):
|
||||
abstract_cls.register(frozen_cls)
|
||||
|
||||
|
||||
class Finder(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Legacy abstract base class for import finders.
|
||||
|
||||
It may be subclassed for compatibility with legacy third party
|
||||
reimplementations of the import system. Otherwise, finder
|
||||
implementations should derive from the more specific MetaPathFinder
|
||||
or PathEntryFinder ABCs.
|
||||
|
||||
Deprecated since Python 3.3
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
warnings.warn("the Finder ABC is deprecated and "
|
||||
"slated for removal in Python 3.12; use MetaPathFinder "
|
||||
"or PathEntryFinder instead",
|
||||
DeprecationWarning)
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_module(self, fullname, path=None):
|
||||
"""An abstract method that should find a module.
|
||||
The fullname is a str and the optional path is a str or None.
|
||||
Returns a Loader object or None.
|
||||
"""
|
||||
warnings.warn("importlib.abc.Finder along with its find_module() "
|
||||
"method are deprecated and "
|
||||
"slated for removal in Python 3.12; use "
|
||||
"MetaPathFinder.find_spec() or "
|
||||
"PathEntryFinder.find_spec() instead",
|
||||
DeprecationWarning)
|
||||
|
||||
|
||||
class MetaPathFinder(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Abstract base class for import finders on sys.meta_path."""
|
||||
@@ -79,27 +56,6 @@ class MetaPathFinder(metaclass=abc.ABCMeta):
|
||||
# We don't define find_spec() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
def find_module(self, fullname, path):
|
||||
"""Return a loader for the module.
|
||||
|
||||
If no module is found, return None. The fullname is a str and
|
||||
the path is a list of strings or None.
|
||||
|
||||
This method is deprecated since Python 3.4 in favor of
|
||||
finder.find_spec(). If find_spec() exists then backwards-compatible
|
||||
functionality is provided for this method.
|
||||
|
||||
"""
|
||||
warnings.warn("MetaPathFinder.find_module() is deprecated since Python "
|
||||
"3.4 in favor of MetaPathFinder.find_spec() and is "
|
||||
"slated for removal in Python 3.12",
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
if not hasattr(self, 'find_spec'):
|
||||
return None
|
||||
found = self.find_spec(fullname, path)
|
||||
return found.loader if found is not None else None
|
||||
|
||||
def invalidate_caches(self):
|
||||
"""An optional method for clearing the finder's cache, if any.
|
||||
This method is used by importlib.invalidate_caches().
|
||||
@@ -113,43 +69,6 @@ class PathEntryFinder(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Abstract base class for path entry finders used by PathFinder."""
|
||||
|
||||
# We don't define find_spec() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
def find_loader(self, fullname):
|
||||
"""Return (loader, namespace portion) for the path entry.
|
||||
|
||||
The fullname is a str. The namespace portion is a sequence of
|
||||
path entries contributing to part of a namespace package. The
|
||||
sequence may be empty. If loader is not None, the portion will
|
||||
be ignored.
|
||||
|
||||
The portion will be discarded if another path entry finder
|
||||
locates the module as a normal module or package.
|
||||
|
||||
This method is deprecated since Python 3.4 in favor of
|
||||
finder.find_spec(). If find_spec() is provided than backwards-compatible
|
||||
functionality is provided.
|
||||
"""
|
||||
warnings.warn("PathEntryFinder.find_loader() is deprecated since Python "
|
||||
"3.4 in favor of PathEntryFinder.find_spec() "
|
||||
"(available since 3.4)",
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
if not hasattr(self, 'find_spec'):
|
||||
return None, []
|
||||
found = self.find_spec(fullname)
|
||||
if found is not None:
|
||||
if not found.submodule_search_locations:
|
||||
portions = []
|
||||
else:
|
||||
portions = found.submodule_search_locations
|
||||
return found.loader, portions
|
||||
else:
|
||||
return None, []
|
||||
|
||||
find_module = _bootstrap_external._find_module_shim
|
||||
|
||||
def invalidate_caches(self):
|
||||
"""An optional method for clearing the finder's cache, if any.
|
||||
This method is used by PathFinder.invalidate_caches().
|
||||
|
||||
305
Lib/importlib/metadata/__init__.py
vendored
305
Lib/importlib/metadata/__init__.py
vendored
@@ -12,7 +12,9 @@ import warnings
|
||||
import functools
|
||||
import itertools
|
||||
import posixpath
|
||||
import contextlib
|
||||
import collections
|
||||
import inspect
|
||||
|
||||
from . import _adapters, _meta
|
||||
from ._collections import FreezableDefaultDict, Pair
|
||||
@@ -24,7 +26,7 @@ from contextlib import suppress
|
||||
from importlib import import_module
|
||||
from importlib.abc import MetaPathFinder
|
||||
from itertools import starmap
|
||||
from typing import List, Mapping, Optional, Union
|
||||
from typing import List, Mapping, Optional, cast
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -140,6 +142,7 @@ class DeprecatedTuple:
|
||||
1
|
||||
"""
|
||||
|
||||
# Do not remove prior to 2023-05-01 or Python 3.13
|
||||
_warn = functools.partial(
|
||||
warnings.warn,
|
||||
"EntryPoint tuple interface is deprecated. Access members by name.",
|
||||
@@ -228,17 +231,6 @@ class EntryPoint(DeprecatedTuple):
|
||||
vars(self).update(dist=dist)
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Supply iter so one may construct dicts of EntryPoints by name.
|
||||
"""
|
||||
msg = (
|
||||
"Construction of dict of EntryPoints is deprecated in "
|
||||
"favor of EntryPoints."
|
||||
)
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
return iter((self.name, self))
|
||||
|
||||
def matches(self, **params):
|
||||
"""
|
||||
EntryPoint matches the given parameters.
|
||||
@@ -284,77 +276,7 @@ class EntryPoint(DeprecatedTuple):
|
||||
return hash(self._key())
|
||||
|
||||
|
||||
class DeprecatedList(list):
|
||||
"""
|
||||
Allow an otherwise immutable object to implement mutability
|
||||
for compatibility.
|
||||
|
||||
>>> recwarn = getfixture('recwarn')
|
||||
>>> dl = DeprecatedList(range(3))
|
||||
>>> dl[0] = 1
|
||||
>>> dl.append(3)
|
||||
>>> del dl[3]
|
||||
>>> dl.reverse()
|
||||
>>> dl.sort()
|
||||
>>> dl.extend([4])
|
||||
>>> dl.pop(-1)
|
||||
4
|
||||
>>> dl.remove(1)
|
||||
>>> dl += [5]
|
||||
>>> dl + [6]
|
||||
[1, 2, 5, 6]
|
||||
>>> dl + (6,)
|
||||
[1, 2, 5, 6]
|
||||
>>> dl.insert(0, 0)
|
||||
>>> dl
|
||||
[0, 1, 2, 5]
|
||||
>>> dl == [0, 1, 2, 5]
|
||||
True
|
||||
>>> dl == (0, 1, 2, 5)
|
||||
True
|
||||
>>> len(recwarn)
|
||||
1
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
_warn = functools.partial(
|
||||
warnings.warn,
|
||||
"EntryPoints list interface is deprecated. Cast to list if needed.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def _wrap_deprecated_method(method_name: str): # type: ignore
|
||||
def wrapped(self, *args, **kwargs):
|
||||
self._warn()
|
||||
return getattr(super(), method_name)(*args, **kwargs)
|
||||
|
||||
return method_name, wrapped
|
||||
|
||||
locals().update(
|
||||
map(
|
||||
_wrap_deprecated_method,
|
||||
'__setitem__ __delitem__ append reverse extend pop remove '
|
||||
'__iadd__ insert sort'.split(),
|
||||
)
|
||||
)
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, tuple):
|
||||
self._warn()
|
||||
other = tuple(other)
|
||||
return self.__class__(tuple(self) + other)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tuple):
|
||||
self._warn()
|
||||
other = tuple(other)
|
||||
|
||||
return tuple(self).__eq__(other)
|
||||
|
||||
|
||||
class EntryPoints(DeprecatedList):
|
||||
class EntryPoints(tuple):
|
||||
"""
|
||||
An immutable collection of selectable EntryPoint objects.
|
||||
"""
|
||||
@@ -365,14 +287,6 @@ class EntryPoints(DeprecatedList):
|
||||
"""
|
||||
Get the EntryPoint in self matching name.
|
||||
"""
|
||||
if isinstance(name, int):
|
||||
warnings.warn(
|
||||
"Accessing entry points by index is deprecated. "
|
||||
"Cast to tuple if needed.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return super().__getitem__(name)
|
||||
try:
|
||||
return next(iter(self.select(name=name)))
|
||||
except StopIteration:
|
||||
@@ -396,10 +310,6 @@ class EntryPoints(DeprecatedList):
|
||||
def groups(self):
|
||||
"""
|
||||
Return the set of all groups of all entry points.
|
||||
|
||||
For coverage while SelectableGroups is present.
|
||||
>>> EntryPoints().groups
|
||||
set()
|
||||
"""
|
||||
return {ep.group for ep in self}
|
||||
|
||||
@@ -415,101 +325,6 @@ class EntryPoints(DeprecatedList):
|
||||
)
|
||||
|
||||
|
||||
class Deprecated:
|
||||
"""
|
||||
Compatibility add-in for mapping to indicate that
|
||||
mapping behavior is deprecated.
|
||||
|
||||
>>> recwarn = getfixture('recwarn')
|
||||
>>> class DeprecatedDict(Deprecated, dict): pass
|
||||
>>> dd = DeprecatedDict(foo='bar')
|
||||
>>> dd.get('baz', None)
|
||||
>>> dd['foo']
|
||||
'bar'
|
||||
>>> list(dd)
|
||||
['foo']
|
||||
>>> list(dd.keys())
|
||||
['foo']
|
||||
>>> 'foo' in dd
|
||||
True
|
||||
>>> list(dd.values())
|
||||
['bar']
|
||||
>>> len(recwarn)
|
||||
1
|
||||
"""
|
||||
|
||||
_warn = functools.partial(
|
||||
warnings.warn,
|
||||
"SelectableGroups dict interface is deprecated. Use select.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def __getitem__(self, name):
|
||||
self._warn()
|
||||
return super().__getitem__(name)
|
||||
|
||||
def get(self, name, default=None):
|
||||
self._warn()
|
||||
return super().get(name, default)
|
||||
|
||||
def __iter__(self):
|
||||
self._warn()
|
||||
return super().__iter__()
|
||||
|
||||
def __contains__(self, *args):
|
||||
self._warn()
|
||||
return super().__contains__(*args)
|
||||
|
||||
def keys(self):
|
||||
self._warn()
|
||||
return super().keys()
|
||||
|
||||
def values(self):
|
||||
self._warn()
|
||||
return super().values()
|
||||
|
||||
|
||||
class SelectableGroups(Deprecated, dict):
|
||||
"""
|
||||
A backward- and forward-compatible result from
|
||||
entry_points that fully implements the dict interface.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def load(cls, eps):
|
||||
by_group = operator.attrgetter('group')
|
||||
ordered = sorted(eps, key=by_group)
|
||||
grouped = itertools.groupby(ordered, by_group)
|
||||
return cls((group, EntryPoints(eps)) for group, eps in grouped)
|
||||
|
||||
@property
|
||||
def _all(self):
|
||||
"""
|
||||
Reconstruct a list of all entrypoints from the groups.
|
||||
"""
|
||||
groups = super(Deprecated, self).values()
|
||||
return EntryPoints(itertools.chain.from_iterable(groups))
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
return self._all.groups
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
"""
|
||||
for coverage:
|
||||
>>> SelectableGroups().names
|
||||
set()
|
||||
"""
|
||||
return self._all.names
|
||||
|
||||
def select(self, **params):
|
||||
if not params:
|
||||
return self
|
||||
return self._all.select(**params)
|
||||
|
||||
|
||||
class PackagePath(pathlib.PurePosixPath):
|
||||
"""A reference to a path in a package"""
|
||||
|
||||
@@ -534,11 +349,30 @@ class FileHash:
|
||||
return f'<FileHash mode: {self.mode} value: {self.value}>'
|
||||
|
||||
|
||||
class Distribution:
|
||||
class DeprecatedNonAbstract:
|
||||
def __new__(cls, *args, **kwargs):
|
||||
all_names = {
|
||||
name for subclass in inspect.getmro(cls) for name in vars(subclass)
|
||||
}
|
||||
abstract = {
|
||||
name
|
||||
for name in all_names
|
||||
if getattr(getattr(cls, name), '__isabstractmethod__', False)
|
||||
}
|
||||
if abstract:
|
||||
warnings.warn(
|
||||
f"Unimplemented abstract methods {abstract}",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return super().__new__(cls)
|
||||
|
||||
|
||||
class Distribution(DeprecatedNonAbstract):
|
||||
"""A Python distribution package."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def read_text(self, filename):
|
||||
def read_text(self, filename) -> Optional[str]:
|
||||
"""Attempt to load metadata file given by the name.
|
||||
|
||||
:param filename: The name of the file in the distribution info.
|
||||
@@ -612,7 +446,7 @@ class Distribution:
|
||||
The returned object will have keys that name the various bits of
|
||||
metadata. See PEP 566 for details.
|
||||
"""
|
||||
text = (
|
||||
opt_text = (
|
||||
self.read_text('METADATA')
|
||||
or self.read_text('PKG-INFO')
|
||||
# This last clause is here to support old egg-info files. Its
|
||||
@@ -620,6 +454,7 @@ class Distribution:
|
||||
# (which points to the egg-info file) attribute unchanged.
|
||||
or self.read_text('')
|
||||
)
|
||||
text = cast(str, opt_text)
|
||||
return _adapters.Message(email.message_from_string(text))
|
||||
|
||||
@property
|
||||
@@ -648,8 +483,8 @@ class Distribution:
|
||||
:return: List of PackagePath for this distribution or None
|
||||
|
||||
Result is `None` if the metadata file that enumerates files
|
||||
(i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
|
||||
missing.
|
||||
(i.e. RECORD for dist-info, or installed-files.txt or
|
||||
SOURCES.txt for egg-info) is missing.
|
||||
Result may be empty if the metadata exists but is empty.
|
||||
"""
|
||||
|
||||
@@ -662,9 +497,19 @@ class Distribution:
|
||||
|
||||
@pass_none
|
||||
def make_files(lines):
|
||||
return list(starmap(make_file, csv.reader(lines)))
|
||||
return starmap(make_file, csv.reader(lines))
|
||||
|
||||
return make_files(self._read_files_distinfo() or self._read_files_egginfo())
|
||||
@pass_none
|
||||
def skip_missing_files(package_paths):
|
||||
return list(filter(lambda path: path.locate().exists(), package_paths))
|
||||
|
||||
return skip_missing_files(
|
||||
make_files(
|
||||
self._read_files_distinfo()
|
||||
or self._read_files_egginfo_installed()
|
||||
or self._read_files_egginfo_sources()
|
||||
)
|
||||
)
|
||||
|
||||
def _read_files_distinfo(self):
|
||||
"""
|
||||
@@ -673,10 +518,45 @@ class Distribution:
|
||||
text = self.read_text('RECORD')
|
||||
return text and text.splitlines()
|
||||
|
||||
def _read_files_egginfo(self):
|
||||
def _read_files_egginfo_installed(self):
|
||||
"""
|
||||
SOURCES.txt might contain literal commas, so wrap each line
|
||||
in quotes.
|
||||
Read installed-files.txt and return lines in a similar
|
||||
CSV-parsable format as RECORD: each file must be placed
|
||||
relative to the site-packages directory and must also be
|
||||
quoted (since file names can contain literal commas).
|
||||
|
||||
This file is written when the package is installed by pip,
|
||||
but it might not be written for other installation methods.
|
||||
Assume the file is accurate if it exists.
|
||||
"""
|
||||
text = self.read_text('installed-files.txt')
|
||||
# Prepend the .egg-info/ subdir to the lines in this file.
|
||||
# But this subdir is only available from PathDistribution's
|
||||
# self._path.
|
||||
subdir = getattr(self, '_path', None)
|
||||
if not text or not subdir:
|
||||
return
|
||||
|
||||
paths = (
|
||||
(subdir / name)
|
||||
.resolve()
|
||||
.relative_to(self.locate_file('').resolve())
|
||||
.as_posix()
|
||||
for name in text.splitlines()
|
||||
)
|
||||
return map('"{}"'.format, paths)
|
||||
|
||||
def _read_files_egginfo_sources(self):
|
||||
"""
|
||||
Read SOURCES.txt and return lines in a similar CSV-parsable
|
||||
format as RECORD: each file name must be quoted (since it
|
||||
might contain literal commas).
|
||||
|
||||
Note that SOURCES.txt is not a reliable source for what
|
||||
files are installed by a package. This file is generated
|
||||
for a source archive, and the files that are present
|
||||
there (e.g. setup.py) may not correctly reflect the files
|
||||
that are present after the package has been installed.
|
||||
"""
|
||||
text = self.read_text('SOURCES.txt')
|
||||
return text and map('"{}"'.format, text.splitlines())
|
||||
@@ -1023,27 +903,19 @@ Wrapper for ``distributions`` to return unique distributions by name.
|
||||
"""
|
||||
|
||||
|
||||
def entry_points(**params) -> Union[EntryPoints, SelectableGroups]:
|
||||
def entry_points(**params) -> EntryPoints:
|
||||
"""Return EntryPoint objects for all installed packages.
|
||||
|
||||
Pass selection parameters (group or name) to filter the
|
||||
result to entry points matching those properties (see
|
||||
EntryPoints.select()).
|
||||
|
||||
For compatibility, returns ``SelectableGroups`` object unless
|
||||
selection parameters are supplied. In the future, this function
|
||||
will return ``EntryPoints`` instead of ``SelectableGroups``
|
||||
even when no selection parameters are supplied.
|
||||
|
||||
For maximum future compatibility, pass selection parameters
|
||||
or invoke ``.select`` with parameters on the result.
|
||||
|
||||
:return: EntryPoints or SelectableGroups for all installed packages.
|
||||
:return: EntryPoints for all installed packages.
|
||||
"""
|
||||
eps = itertools.chain.from_iterable(
|
||||
dist.entry_points for dist in _unique(distributions())
|
||||
)
|
||||
return SelectableGroups.load(eps).select(**params)
|
||||
return EntryPoints(eps).select(**params)
|
||||
|
||||
|
||||
def files(distribution_name):
|
||||
@@ -1087,8 +959,13 @@ def _top_level_declared(dist):
|
||||
|
||||
|
||||
def _top_level_inferred(dist):
|
||||
return {
|
||||
f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
|
||||
opt_names = {
|
||||
f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f)
|
||||
for f in always_iterable(dist.files)
|
||||
if f.suffix == ".py"
|
||||
}
|
||||
|
||||
@pass_none
|
||||
def importable_name(name):
|
||||
return '.' not in name
|
||||
|
||||
return filter(importable_name, opt_names)
|
||||
|
||||
21
Lib/importlib/metadata/_adapters.py
vendored
21
Lib/importlib/metadata/_adapters.py
vendored
@@ -1,3 +1,5 @@
|
||||
import functools
|
||||
import warnings
|
||||
import re
|
||||
import textwrap
|
||||
import email.message
|
||||
@@ -5,6 +7,15 @@ import email.message
|
||||
from ._text import FoldedCase
|
||||
|
||||
|
||||
# Do not remove prior to 2024-01-01 or Python 3.14
|
||||
_warn = functools.partial(
|
||||
warnings.warn,
|
||||
"Implicit None on return values is deprecated and will raise KeyErrors.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
class Message(email.message.Message):
|
||||
multiple_use_keys = set(
|
||||
map(
|
||||
@@ -39,6 +50,16 @@ class Message(email.message.Message):
|
||||
def __iter__(self):
|
||||
return super().__iter__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""
|
||||
Warn users that a ``KeyError`` can be expected when a
|
||||
mising key is supplied. Ref python/importlib_metadata#371.
|
||||
"""
|
||||
res = super().__getitem__(item)
|
||||
if res is None:
|
||||
_warn()
|
||||
return res
|
||||
|
||||
def _repair_headers(self):
|
||||
def redent(value):
|
||||
"Correct for RFC822 indentation"
|
||||
|
||||
28
Lib/importlib/metadata/_meta.py
vendored
28
Lib/importlib/metadata/_meta.py
vendored
@@ -1,4 +1,5 @@
|
||||
from typing import Any, Dict, Iterator, List, Protocol, TypeVar, Union
|
||||
from typing import Protocol
|
||||
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
@@ -17,7 +18,21 @@ class PackageMetadata(Protocol):
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
... # pragma: no cover
|
||||
|
||||
def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]:
|
||||
@overload
|
||||
def get(self, name: str, failobj: None = None) -> Optional[str]:
|
||||
... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get(self, name: str, failobj: _T) -> Union[str, _T]:
|
||||
... # pragma: no cover
|
||||
|
||||
# overload per python/importlib_metadata#435
|
||||
@overload
|
||||
def get_all(self, name: str, failobj: None = None) -> Optional[List[Any]]:
|
||||
... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
|
||||
"""
|
||||
Return all values associated with a possibly multi-valued key.
|
||||
"""
|
||||
@@ -29,18 +44,19 @@ class PackageMetadata(Protocol):
|
||||
"""
|
||||
|
||||
|
||||
class SimplePath(Protocol):
|
||||
class SimplePath(Protocol[_T]):
|
||||
"""
|
||||
A minimal subset of pathlib.Path required by PathDistribution.
|
||||
"""
|
||||
|
||||
def joinpath(self) -> 'SimplePath':
|
||||
def joinpath(self) -> _T:
|
||||
... # pragma: no cover
|
||||
|
||||
def __truediv__(self) -> 'SimplePath':
|
||||
def __truediv__(self, other: Union[str, _T]) -> _T:
|
||||
... # pragma: no cover
|
||||
|
||||
def parent(self) -> 'SimplePath':
|
||||
@property
|
||||
def parent(self) -> _T:
|
||||
... # pragma: no cover
|
||||
|
||||
def read_text(self) -> str:
|
||||
|
||||
4
Lib/importlib/resources/_adapters.py
vendored
4
Lib/importlib/resources/_adapters.py
vendored
@@ -34,9 +34,7 @@ def _io_wrapper(file, mode='r', *args, **kwargs):
|
||||
return TextIOWrapper(file, *args, **kwargs)
|
||||
elif mode == 'rb':
|
||||
return file
|
||||
raise ValueError(
|
||||
"Invalid mode value '{}', only 'r' and 'rb' are supported".format(mode)
|
||||
)
|
||||
raise ValueError(f"Invalid mode value '{mode}', only 'r' and 'rb' are supported")
|
||||
|
||||
|
||||
class CompatibilityFiles:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user