mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Compare commits
459 Commits
typelock
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23813ddbcd | ||
|
|
1ce37bf2d4 | ||
|
|
0e66439212 | ||
|
|
8018ba689f | ||
|
|
26f817a55b | ||
|
|
938d42184f | ||
|
|
1385c4e472 | ||
|
|
fc637a155d | ||
|
|
7d54ba502e | ||
|
|
e80a14ba12 | ||
|
|
30ae48b24b | ||
|
|
1a959cf7f3 | ||
|
|
ca412fce5d | ||
|
|
f95b7468f7 | ||
|
|
1cb24c5ebb | ||
|
|
ce79cd4853 | ||
|
|
dcb273ba68 | ||
|
|
dd8d250ba3 | ||
|
|
3db172c025 | ||
|
|
fb531ecce3 | ||
|
|
9701c46d86 | ||
|
|
021c78655e | ||
|
|
411230a46c | ||
|
|
b69644196a | ||
|
|
a6fee92683 | ||
|
|
f4696ea890 | ||
|
|
0237a1d520 | ||
|
|
6bbe24a725 | ||
|
|
f9e4988cf5 | ||
|
|
a5775e0c07 | ||
|
|
bc3d00e879 | ||
|
|
52305c0c72 | ||
|
|
7011942e4e | ||
|
|
438925401f | ||
|
|
2fabf38d8f | ||
|
|
d7d936575c | ||
|
|
b5ff41c219 | ||
|
|
e1d9a1123e | ||
|
|
b9efe10537 | ||
|
|
d3272e752b | ||
|
|
f3b83efcee | ||
|
|
1a013930a7 | ||
|
|
c513b923df | ||
|
|
cf3b6397b2 | ||
|
|
2a163609cd | ||
|
|
4eb9534646 | ||
|
|
ab5bc43359 | ||
|
|
06f73f2ae1 | ||
|
|
c845861c4f | ||
|
|
a136f9047b | ||
|
|
948924a14b | ||
|
|
ae6c16093e | ||
|
|
a6f13b98fb | ||
|
|
4daa526dc1 | ||
|
|
d073964aaa | ||
|
|
ee006af13e | ||
|
|
de06fc0923 | ||
|
|
ae3804fb21 | ||
|
|
d51069bc7f | ||
|
|
0063a6d18b | ||
|
|
cea21f953d | ||
|
|
62b081b893 | ||
|
|
26f5bbf077 | ||
|
|
ccf1508a06 | ||
|
|
c5143aa82f | ||
|
|
0a2461a704 | ||
|
|
276a0e6c42 | ||
|
|
d8dee81157 | ||
|
|
e8d7437d91 | ||
|
|
53941295a2 | ||
|
|
1fe4485c10 | ||
|
|
50f235aded | ||
|
|
b902ffdcf8 | ||
|
|
9c0557820b | ||
|
|
150b9103a8 | ||
|
|
48ad238349 | ||
|
|
67e66bdc75 | ||
|
|
e16f1aa657 | ||
|
|
6e56d935cf | ||
|
|
20cb8843e0 | ||
|
|
a4579a98b2 | ||
|
|
883ce9d273 | ||
|
|
723766ef69 | ||
|
|
3ed8c91fef | ||
|
|
a1a87dc8ca | ||
|
|
ea2a3d9d84 | ||
|
|
9c188b4da9 | ||
|
|
ab72b292bd | ||
|
|
f4f035013d | ||
|
|
c77c56d99c | ||
|
|
b432d4cbdc | ||
|
|
5a5230a400 | ||
|
|
8c988711dd | ||
|
|
f2ef252724 | ||
|
|
3a56f37505 | ||
|
|
4b9416a558 | ||
|
|
2d8f8ab818 | ||
|
|
a364f86fd5 | ||
|
|
7273d76cf2 | ||
|
|
54589bf255 | ||
|
|
0871bc8a2d | ||
|
|
f26016049d | ||
|
|
451bdcc9da | ||
|
|
8253253fc7 | ||
|
|
56269f704a | ||
|
|
078e23de27 | ||
|
|
ddfcb25957 | ||
|
|
e467b67ef7 | ||
|
|
460b1d39ed | ||
|
|
11e991fb95 | ||
|
|
ef375bec26 | ||
|
|
4059a032a7 | ||
|
|
e8711edd2d | ||
|
|
f8e0eeb579 | ||
|
|
32e6f8dd81 | ||
|
|
e36cd994e8 | ||
|
|
7ebffd063b | ||
|
|
5ef91c22de | ||
|
|
79395de9f3 | ||
|
|
2b19ba79a8 | ||
|
|
9d77b25858 | ||
|
|
d09179a7ee | ||
|
|
8fe23b8a15 | ||
|
|
ff05dae11c | ||
|
|
1da29ff003 | ||
|
|
a702e9ccc7 | ||
|
|
9ee27526bc | ||
|
|
fb37b5ecc6 | ||
|
|
3bfa5ab8c0 | ||
|
|
a0632ae2c5 | ||
|
|
f3c2198ff0 | ||
|
|
2bcbe96e6d | ||
|
|
f5357692e8 | ||
|
|
e0689bad7c | ||
|
|
1ac55db966 | ||
|
|
a7c9b98b5a | ||
|
|
2bb7dd8cf9 | ||
|
|
f6c382c595 | ||
|
|
72679af4b2 | ||
|
|
27db8e5847 | ||
|
|
68be554684 | ||
|
|
2e5077fe12 | ||
|
|
673db1d71a | ||
|
|
3cfb0fe7bc | ||
|
|
9e066c4f50 | ||
|
|
3bce5566fa | ||
|
|
3b67d0f5d2 | ||
|
|
77070eb6ca | ||
|
|
d6abdc4b79 | ||
|
|
fc00fc22d2 | ||
|
|
865e75dd99 | ||
|
|
9f019a5b86 | ||
|
|
ad6cc8f0a2 | ||
|
|
4a46e84eb9 | ||
|
|
949a620811 | ||
|
|
315865a6f7 | ||
|
|
c79aefecee | ||
|
|
3fbfbf53c2 | ||
|
|
3bd79e6b5b | ||
|
|
8e8b70fb33 | ||
|
|
29530049fe | ||
|
|
5b0177d20a | ||
|
|
479b2dc997 | ||
|
|
cb0cffbd7c | ||
|
|
cc829b2756 | ||
|
|
dfe99f647d | ||
|
|
b3098c3058 | ||
|
|
8f19dff19d | ||
|
|
320355f633 | ||
|
|
108461f637 | ||
|
|
bf7bb1bf3b | ||
|
|
09c27bb440 | ||
|
|
6dfc68b225 | ||
|
|
17376ace28 | ||
|
|
9eacdfca48 | ||
|
|
68a0bc030b | ||
|
|
edcf3002de | ||
|
|
67630ffbfe | ||
|
|
6ed17c3cb5 | ||
|
|
002fc1122e | ||
|
|
ae5c9119c9 | ||
|
|
4aa73ddab2 | ||
|
|
7576d68060 | ||
|
|
3bab7a9086 | ||
|
|
e10a27b1ae | ||
|
|
3b364608d9 | ||
|
|
1c4361803d | ||
|
|
22d4f43ad4 | ||
|
|
02a2b19839 | ||
|
|
ae7ff9c481 | ||
|
|
d877c30417 | ||
|
|
8bd4c5137e | ||
|
|
dc12bff0f4 | ||
|
|
e1dd3d43d5 | ||
|
|
a5cebc3242 | ||
|
|
ad5a55c1e3 | ||
|
|
e4d35b08ea | ||
|
|
02932384d6 | ||
|
|
0325fd429e | ||
|
|
4db0aca471 | ||
|
|
83002d7369 | ||
|
|
90cc888f4b | ||
|
|
cf23884656 | ||
|
|
c3c9292c8b | ||
|
|
ee5e9d0001 | ||
|
|
181e4e7124 | ||
|
|
eb99a8ecf1 | ||
|
|
acc167fc44 | ||
|
|
c2910c06f3 | ||
|
|
ac3e067230 | ||
|
|
be7841f9c1 | ||
|
|
3e66fb508a | ||
|
|
f2ad84a489 | ||
|
|
926d69b50a | ||
|
|
8d1c68c9a0 | ||
|
|
d9fff99718 | ||
|
|
0f25d145fd | ||
|
|
7fd0da92d3 | ||
|
|
c027ffc2ba | ||
|
|
192ba371e4 | ||
|
|
d25195ed0e | ||
|
|
9004089fd1 | ||
|
|
f6e2fcffe7 | ||
|
|
bb77ac6284 | ||
|
|
c2141a765f | ||
|
|
dd1cbac692 | ||
|
|
c98d26e508 | ||
|
|
5e408d65f4 | ||
|
|
73b5b69bd8 | ||
|
|
e79df4a6fb | ||
|
|
32e16fe7da | ||
|
|
88be7bb16a | ||
|
|
f90a5cf650 | ||
|
|
6c2c8421d7 | ||
|
|
6e895fbac4 | ||
|
|
543fcc841c | ||
|
|
6c91c5bb2a | ||
|
|
3c297d478a | ||
|
|
6ed2f15b67 | ||
|
|
6b67067e9a | ||
|
|
04ffa3891c | ||
|
|
ba2b619c0c | ||
|
|
c8ddbd2326 | ||
|
|
3ebcab70c0 | ||
|
|
51e7200d11 | ||
|
|
d7a319d967 | ||
|
|
330b18f2fe | ||
|
|
82e8b200db | ||
|
|
3f718f9942 | ||
|
|
363d19839f | ||
|
|
68aece59c9 | ||
|
|
b3d6d2f247 | ||
|
|
e6d9ea6bfe | ||
|
|
59382f385a | ||
|
|
9db00f741c | ||
|
|
e5f2d2d3b9 | ||
|
|
7fb743b1be | ||
|
|
6c498fc4a7 | ||
|
|
b8f7ae4265 | ||
|
|
1d42ee565f | ||
|
|
9794ab7fdf | ||
|
|
dc81c740cf | ||
|
|
f10f441854 | ||
|
|
1fa676fd07 | ||
|
|
5648a3346f | ||
|
|
02c454bdb4 | ||
|
|
049d44b1e0 | ||
|
|
f6b6b18b62 | ||
|
|
7f8cdddbbf | ||
|
|
625e5bf012 | ||
|
|
a2afaf0f13 | ||
|
|
956267c49e | ||
|
|
be43bb6dbf | ||
|
|
6ab1f806ba | ||
|
|
f0e23aacc2 | ||
|
|
a5f48eaaa1 | ||
|
|
b427f31164 | ||
|
|
7df0801db3 | ||
|
|
3a793ce716 | ||
|
|
adafaf222b | ||
|
|
f6371de4a1 | ||
|
|
fb1218d6ba | ||
|
|
3f8a0b12eb | ||
|
|
2e5c2be7fa | ||
|
|
0d67fd69e2 | ||
|
|
952be48944 | ||
|
|
0913563bbe | ||
|
|
1ab76d012b | ||
|
|
f0acc67855 | ||
|
|
9ebdf10c11 | ||
|
|
7bb2fb0755 | ||
|
|
43ef2eabbe | ||
|
|
dc0c814671 | ||
|
|
a7ea01a135 | ||
|
|
cbfb313de2 | ||
|
|
adb169e65b | ||
|
|
5081f76faf | ||
|
|
f2e055f7d6 | ||
|
|
f2f20175b3 | ||
|
|
a693a0c8aa | ||
|
|
71380bead9 | ||
|
|
5a45d41df0 | ||
|
|
9b0c668f74 | ||
|
|
dc65255fd2 | ||
|
|
a9fd4bf41f | ||
|
|
5058090a3f | ||
|
|
b929a50647 | ||
|
|
f8862e4eed | ||
|
|
18c6c16e2a | ||
|
|
d5921d16af | ||
|
|
9140ef583a | ||
|
|
af41d11faf | ||
|
|
175f12b664 | ||
|
|
b18b71b2db | ||
|
|
fdb49d83c5 | ||
|
|
37707081f8 | ||
|
|
764e4de061 | ||
|
|
b842a6c6c6 | ||
|
|
9669118d3c | ||
|
|
67eedddcd7 | ||
|
|
57ca1d59a6 | ||
|
|
9a0410dab4 | ||
|
|
b80c2bd5ec | ||
|
|
a5b9f0e80b | ||
|
|
caf8d55da5 | ||
|
|
c79baa3317 | ||
|
|
f0bf8100c9 | ||
|
|
1f1be5e29e | ||
|
|
4f1cf6d401 | ||
|
|
3e1aa7cbe6 | ||
|
|
b9f9ba145e | ||
|
|
3d91197b38 | ||
|
|
2827eca293 | ||
|
|
aac207003f | ||
|
|
f82b8d8eb7 | ||
|
|
8d61a2217b | ||
|
|
640cbd7c4a | ||
|
|
aa12accdac | ||
|
|
fd2117355e | ||
|
|
36025386f3 | ||
|
|
330aa08488 | ||
|
|
a2c3e65b81 | ||
|
|
8108b6a153 | ||
|
|
63a1c0e95c | ||
|
|
c98939a7c1 | ||
|
|
9f1429d95f | ||
|
|
73218f42d5 | ||
|
|
f197699e3c | ||
|
|
898da7f58c | ||
|
|
0ee07e3d0a | ||
|
|
49048504f6 | ||
|
|
e3997ad1b8 | ||
|
|
da01e617de | ||
|
|
891538d924 | ||
|
|
3096d77ec5 | ||
|
|
9e2d03428c | ||
|
|
d1e9763ff3 | ||
|
|
2b1b0ba805 | ||
|
|
e09b66dd86 | ||
|
|
a24ee58961 | ||
|
|
1721f62804 | ||
|
|
8f71ff4b47 | ||
|
|
4bbabe5810 | ||
|
|
7544628268 | ||
|
|
7e637e8cbd | ||
|
|
7e5e026941 | ||
|
|
27aed85599 | ||
|
|
d201c48e1c | ||
|
|
9cf7bcd64a | ||
|
|
31edcfa97e | ||
|
|
eac45727d2 | ||
|
|
28acbc66f9 | ||
|
|
a49ce5bf34 | ||
|
|
7b5ac61026 | ||
|
|
ad66d9acd0 | ||
|
|
00dd9a5ed1 | ||
|
|
d5a90e5c1f | ||
|
|
72f397c6df | ||
|
|
e009cc0c3b | ||
|
|
eed618d858 | ||
|
|
87fc4540c4 | ||
|
|
a09afab912 | ||
|
|
3d9688402a | ||
|
|
b61dfdc534 | ||
|
|
6d7d74cc0b | ||
|
|
3f49f42702 | ||
|
|
5afa3493a1 | ||
|
|
1adda8a73d | ||
|
|
344b7a5abd | ||
|
|
d9c4c95369 | ||
|
|
403c2be01d | ||
|
|
5cc9eab2dd | ||
|
|
b275a90cf9 | ||
|
|
43851c21b9 | ||
|
|
611b122ed7 | ||
|
|
1a4964b741 | ||
|
|
106f1c9f37 | ||
|
|
c45f69977b | ||
|
|
2703f94c3e | ||
|
|
9900c761ca | ||
|
|
959b088d25 | ||
|
|
1c39fdb7f9 | ||
|
|
3706c5376e | ||
|
|
e6bcd64066 | ||
|
|
2ebd7026e4 | ||
|
|
6826557884 | ||
|
|
1f6b4c6bf1 | ||
|
|
b9cbd5133b | ||
|
|
902985def7 | ||
|
|
90c5464901 | ||
|
|
da440dbbbe | ||
|
|
1a9b10ece5 | ||
|
|
dd632363c8 | ||
|
|
f7556b00c1 | ||
|
|
fab1c0cc01 | ||
|
|
5dd88ee5ae | ||
|
|
2722bc06de | ||
|
|
3dae07cd60 | ||
|
|
fddd7cb690 | ||
|
|
410721740d | ||
|
|
e3ac1bf8dc | ||
|
|
3a8fb76014 | ||
|
|
a91127c91a | ||
|
|
af0c2526a7 | ||
|
|
f42ffd61a1 | ||
|
|
3f92c3ad1c | ||
|
|
9282a870db | ||
|
|
7a6dbd6624 | ||
|
|
6c3dd2885d | ||
|
|
c9cfb3d606 | ||
|
|
e1ecb87f32 | ||
|
|
ea5a6cd9c0 | ||
|
|
6b5c5a9e92 | ||
|
|
211649d148 | ||
|
|
4ebc3112d9 | ||
|
|
6db7910ca4 | ||
|
|
8d3bc4cb54 | ||
|
|
20c6505bb9 | ||
|
|
372280ede4 | ||
|
|
82432be962 | ||
|
|
40c84f51c8 | ||
|
|
5408627594 | ||
|
|
fb6520e5cc | ||
|
|
e9b45a1419 | ||
|
|
2acf76bbaf | ||
|
|
dc95db7ae3 | ||
|
|
20ae3ccda2 | ||
|
|
f1d0fc31c5 | ||
|
|
56c3a37266 | ||
|
|
8c016157f4 | ||
|
|
907ce4d895 | ||
|
|
2180f535d8 | ||
|
|
3c62b5679f | ||
|
|
dfcb07cd93 | ||
|
|
2d676e7f4d | ||
|
|
3e9f825e1d | ||
|
|
4abe4c5bf0 | ||
|
|
a1203ae207 | ||
|
|
5b6a479a1d |
@@ -4,6 +4,8 @@ argdefs
|
||||
argtypes
|
||||
asdl
|
||||
asname
|
||||
atopen
|
||||
atext
|
||||
attro
|
||||
augassign
|
||||
badcert
|
||||
@@ -30,9 +32,12 @@ cellvar
|
||||
cellvars
|
||||
ceval
|
||||
cfield
|
||||
cfws
|
||||
CFWS
|
||||
CLASSDEREF
|
||||
classdict
|
||||
cmpop
|
||||
CNOTAB
|
||||
codedepth
|
||||
CODEUNIT
|
||||
CONIN
|
||||
@@ -47,13 +52,16 @@ datastack
|
||||
defaultdict
|
||||
denom
|
||||
deopt
|
||||
deopts
|
||||
dictbytype
|
||||
DICTFLAG
|
||||
dictoffset
|
||||
distpoint
|
||||
dynload
|
||||
elts
|
||||
eooh
|
||||
eofs
|
||||
EOOH
|
||||
evalloop
|
||||
excepthandler
|
||||
exceptiontable
|
||||
@@ -62,6 +70,7 @@ fastlocals
|
||||
fblock
|
||||
fblocks
|
||||
fdescr
|
||||
fdst
|
||||
ffi_argtypes
|
||||
fielddesc
|
||||
fieldlist
|
||||
@@ -75,6 +84,7 @@ freelist
|
||||
freevar
|
||||
freevars
|
||||
fromlist
|
||||
fsrc
|
||||
getdict
|
||||
getfunc
|
||||
getiter
|
||||
@@ -89,27 +99,39 @@ HASUNION
|
||||
heaptype
|
||||
hexdigit
|
||||
HIGHRES
|
||||
ialloc
|
||||
IFUNC
|
||||
IMMUTABLETYPE
|
||||
INCREF
|
||||
inlinedepth
|
||||
inplace
|
||||
inpos
|
||||
ioffset
|
||||
isbytecode
|
||||
ishidden
|
||||
ismine
|
||||
ISPOINTER
|
||||
isoctal
|
||||
iteminfo
|
||||
Itertool
|
||||
iused
|
||||
keeped
|
||||
kwnames
|
||||
kwonlyarg
|
||||
kwonlyargs
|
||||
kwonlydefaults
|
||||
lasti
|
||||
libffi
|
||||
linearise
|
||||
lineful
|
||||
lineiterator
|
||||
linetable
|
||||
LNOTAB
|
||||
loadfast
|
||||
localsplus
|
||||
localspluskinds
|
||||
Lshift
|
||||
lslpp
|
||||
lsprof
|
||||
MAXBLOCKS
|
||||
maxdepth
|
||||
@@ -119,16 +141,23 @@ mult
|
||||
multibytecodec
|
||||
nameobj
|
||||
nameop
|
||||
nargsf
|
||||
nblocks
|
||||
ncells
|
||||
ncellsused
|
||||
ncellvars
|
||||
nconsts
|
||||
newargs
|
||||
newfree
|
||||
NEWLOCALS
|
||||
newsemlockobject
|
||||
nextop
|
||||
nfrees
|
||||
nkwargs
|
||||
nkwelts
|
||||
nlocalsplus
|
||||
nointerrupt
|
||||
noffsets
|
||||
Nondescriptor
|
||||
noninteger
|
||||
nops
|
||||
@@ -136,6 +165,7 @@ noraise
|
||||
nseen
|
||||
NSIGNALS
|
||||
numer
|
||||
nvars
|
||||
opname
|
||||
opnames
|
||||
orelse
|
||||
@@ -148,18 +178,22 @@ patma
|
||||
peepholer
|
||||
phcount
|
||||
platstdlib
|
||||
ploc
|
||||
posonlyarg
|
||||
posonlyargs
|
||||
prec
|
||||
preds
|
||||
preinitialized
|
||||
pybuilddir
|
||||
pycore
|
||||
pyinner
|
||||
pydecimal
|
||||
pyerrors
|
||||
Pyfunc
|
||||
pylifecycle
|
||||
pymain
|
||||
pyrepl
|
||||
pystate
|
||||
PYTHONTRACEMALLOC
|
||||
PYTHONUTF8
|
||||
pythonw
|
||||
@@ -167,6 +201,7 @@ PYTHREAD_NAME
|
||||
releasebuffer
|
||||
repr
|
||||
resinfo
|
||||
retarget
|
||||
Rshift
|
||||
SA_ONSTACK
|
||||
saveall
|
||||
@@ -178,6 +213,7 @@ SETREF
|
||||
setresult
|
||||
setslice
|
||||
settraceallthreads
|
||||
sget
|
||||
SLOTDEFINED
|
||||
SMALLBUF
|
||||
SOABI
|
||||
@@ -188,13 +224,16 @@ staticbase
|
||||
stginfo
|
||||
storefast
|
||||
stringlib
|
||||
stringized
|
||||
structseq
|
||||
subkwargs
|
||||
subparams
|
||||
subscr
|
||||
sval
|
||||
swappedbytes
|
||||
swaptimize
|
||||
sysdict
|
||||
tbstderr
|
||||
templatelib
|
||||
testconsole
|
||||
threadstate
|
||||
@@ -213,6 +252,7 @@ uncollectable
|
||||
Unhandle
|
||||
unparse
|
||||
unparser
|
||||
untargeted
|
||||
untracking
|
||||
VARKEYWORDS
|
||||
varkwarg
|
||||
|
||||
@@ -67,6 +67,7 @@ fillchar
|
||||
fillvalue
|
||||
finallyhandler
|
||||
firstiter
|
||||
fobj
|
||||
firstlineno
|
||||
fnctl
|
||||
frombytes
|
||||
@@ -111,12 +112,14 @@ idfunc
|
||||
idiv
|
||||
idxs
|
||||
impls
|
||||
infd
|
||||
indexgroup
|
||||
infj
|
||||
inittab
|
||||
Inittab
|
||||
instancecheck
|
||||
instanceof
|
||||
instrs
|
||||
interpchannels
|
||||
interpqueues
|
||||
irepeat
|
||||
@@ -175,6 +178,7 @@ Nonprintable
|
||||
onceregistry
|
||||
origname
|
||||
ospath
|
||||
outfd
|
||||
pendingcr
|
||||
phello
|
||||
platlibdir
|
||||
@@ -185,10 +189,12 @@ posonlyargcount
|
||||
prepending
|
||||
profilefunc
|
||||
pycache
|
||||
pycapsule
|
||||
pycodecs
|
||||
pycs
|
||||
pydatetime
|
||||
pyexpat
|
||||
PYGILSTATE
|
||||
pyio
|
||||
pymain
|
||||
PYTHONAPI
|
||||
|
||||
13
.cspell.json
13
.cspell.json
@@ -49,20 +49,25 @@
|
||||
"ignorePaths": [
|
||||
"**/__pycache__/**",
|
||||
"target/**",
|
||||
"Lib/**"
|
||||
"Lib/**",
|
||||
"crates/host_env/**"
|
||||
],
|
||||
// words - list of words to be always considered correct
|
||||
// (compound words like pyarg, baseclass, microbenchmark are handled by allowCompoundWords)
|
||||
"words": [
|
||||
"aiterable",
|
||||
"alnum",
|
||||
"csock",
|
||||
"coro",
|
||||
"dedentations",
|
||||
"dedents",
|
||||
"deduped",
|
||||
"deoptimized",
|
||||
"deoptimize",
|
||||
"emscripten",
|
||||
"excs",
|
||||
"fnfe",
|
||||
"ifexp",
|
||||
"interps",
|
||||
"jitted",
|
||||
"jitting",
|
||||
@@ -70,9 +75,15 @@
|
||||
"lossily",
|
||||
"mcache",
|
||||
"oparg",
|
||||
"opargs",
|
||||
"pyc",
|
||||
"reborrow",
|
||||
"reraises",
|
||||
"reraising",
|
||||
"significand",
|
||||
"summands",
|
||||
"TESTFN",
|
||||
"TZPATH",
|
||||
"unraisable",
|
||||
"wasi",
|
||||
"weaked",
|
||||
|
||||
19
.gitattributes
vendored
19
.gitattributes
vendored
@@ -58,13 +58,14 @@ Lib/venv/scripts/posix/* text eol=lf
|
||||
#
|
||||
[attr]generated linguist-generated=true diff=generated
|
||||
|
||||
Lib/_opcode_metadata.py generated
|
||||
Lib/keyword.py generated
|
||||
Lib/idlelib/help.html generated
|
||||
Lib/test/certdata/*.pem generated
|
||||
Lib/test/certdata/*.0 generated
|
||||
Lib/test/levenshtein_examples.json generated
|
||||
Lib/test/test_stable_abi_ctypes.py generated
|
||||
Lib/token.py generated
|
||||
Lib/_opcode_metadata.py generated
|
||||
Lib/keyword.py generated
|
||||
Lib/idlelib/help.html generated
|
||||
Lib/test/certdata/*.pem generated
|
||||
Lib/test/certdata/*.0 generated
|
||||
Lib/test/levenshtein_examples.json generated
|
||||
Lib/test/test_stable_abi_ctypes.py generated
|
||||
Lib/token.py generated
|
||||
crates/compiler-core/src/bytecode/opcode_metadata.rs generated
|
||||
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
Thanks for your contribution!
|
||||
-->
|
||||
|
||||
- [ ] Closes #xxxx <!-- Replace xxxx with the GitHub issue number -->
|
||||
- [ ] This PR follows our [AI policy](https://github.com/RustPython/.github/blob/main/AI_POLICY.md)
|
||||
|
||||
## Summary
|
||||
<!-- What's the purpose of the change? What does it do, and why? -->
|
||||
|
||||
28
.github/dependabot.yml
vendored
28
.github/dependabot.yml
vendored
@@ -2,9 +2,16 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
directories:
|
||||
- "/"
|
||||
- "crates/*"
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
semver-major-days: 30
|
||||
semver-minor-days: 7
|
||||
semver-patch-days: 3
|
||||
groups:
|
||||
criterion:
|
||||
patterns:
|
||||
@@ -14,6 +21,7 @@ updates:
|
||||
- "digest"
|
||||
- "md-5"
|
||||
- "sha-1"
|
||||
- "sha1"
|
||||
- "sha2"
|
||||
- "sha3"
|
||||
- "blake2"
|
||||
@@ -115,6 +123,11 @@ updates:
|
||||
toml:
|
||||
patterns:
|
||||
- "toml*"
|
||||
unix:
|
||||
patterns:
|
||||
- "mac_address"
|
||||
- "nix"
|
||||
- "rustyline"
|
||||
wasm-bindgen:
|
||||
patterns:
|
||||
- "wasm-bindgen*"
|
||||
@@ -143,7 +156,20 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
semver-major-days: 30
|
||||
semver-minor-days: 7
|
||||
semver-patch-days: 3
|
||||
- package-ecosystem: pre-commit
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
592
.github/workflows/ci.yaml
vendored
592
.github/workflows/ci.yaml
vendored
@@ -8,27 +8,74 @@ on:
|
||||
|
||||
name: CI
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Cancel previous workflows if they are the same workflow on same ref (branch/tags)
|
||||
# with the same event (push/pull_request) even they are in progress.
|
||||
# This setting will help reduce the number of duplicated workflows.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls,host_env
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env
|
||||
CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,host_env
|
||||
# Crates excluded from workspace builds:
|
||||
# - rustpython_wasm: requires wasm target
|
||||
# - rustpython-compiler-source: deprecated
|
||||
# - rustpython-venvlauncher: Windows-only
|
||||
WORKSPACE_EXCLUDES: --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
|
||||
# Python version targeted by the CI.
|
||||
PYTHON_VERSION: "3.14.3"
|
||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
|
||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
CARGO_PROFILE_RELEASE_DEBUG: 0
|
||||
CARGO_TERM_COLOR: always
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # TODO: Remove on 2026/06/02
|
||||
CI: true
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
name: Determine changes
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
# Flag that is raised when any rust code is changed.
|
||||
rust_code: ${{ steps.check_rust_code.outputs.changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Determine merge base
|
||||
id: merge_base
|
||||
run: |
|
||||
sha=$(git merge-base HEAD "origin/${BASE_REF}")
|
||||
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
|
||||
|
||||
- name: Check if there was any code related change
|
||||
id: check_rust_code
|
||||
run: |
|
||||
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
|
||||
':Cargo.toml' \
|
||||
':Cargo.lock' \
|
||||
':rust-toolchain.toml' \
|
||||
':.cargo/config.toml' \
|
||||
':crates/**' \
|
||||
':src/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
env:
|
||||
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
|
||||
|
||||
rust_tests:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||
env:
|
||||
@@ -46,24 +93,35 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install macOS dependencies
|
||||
uses: ./.github/actions/install-macos-deps
|
||||
|
||||
- name: run clippy
|
||||
run: cargo clippy ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings
|
||||
|
||||
- name: run rust tests
|
||||
run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --verbose --features threading ${{ env.CARGO_ARGS }}
|
||||
run: cargo test --workspace --exclude rustpython-capi ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }}
|
||||
env:
|
||||
INSTA_WORKSPACE_ROOT: ${{ github.workspace }}
|
||||
|
||||
- name: check compilation without threading
|
||||
run: cargo check ${{ env.CARGO_ARGS }}
|
||||
- name: run c-api tests
|
||||
working-directory: crates/capi
|
||||
run: cargo test
|
||||
if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows
|
||||
|
||||
- name: check compilation without host_env (sandbox mode)
|
||||
run: |
|
||||
@@ -82,6 +140,10 @@ jobs:
|
||||
run: cargo build --no-default-features --features ssl-openssl
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Test vendored OpenSSL build
|
||||
run: cargo build --no-default-features --features ssl-openssl-vendor
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
# - name: Install tk-dev for tkinter build
|
||||
# run: sudo apt-get update && sudo apt-get install -y tk-dev
|
||||
# if: runner.os == 'Linux'
|
||||
@@ -103,42 +165,49 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
cargo_check:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||
name: Ensure compilation on various targets
|
||||
name: cargo check
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs:
|
||||
- determine_changes
|
||||
if: |
|
||||
(
|
||||
!contains(github.event.pull_request.labels.*.name, 'skip:ci') &&
|
||||
needs.determine_changes.outputs.rust_code == 'true'
|
||||
) || github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
targets:
|
||||
- aarch64-linux-android
|
||||
- i686-unknown-linux-gnu
|
||||
- i686-unknown-linux-musl
|
||||
- wasm32-wasip2
|
||||
- x86_64-unknown-freebsd
|
||||
target: aarch64-linux-android
|
||||
- os: ubuntu-latest
|
||||
target: i686-unknown-linux-gnu
|
||||
dependencies:
|
||||
gcc-multilib: true
|
||||
musl-tools: true
|
||||
- os: ubuntu-latest
|
||||
targets:
|
||||
- aarch64-unknown-linux-gnu
|
||||
target: i686-unknown-linux-musl
|
||||
dependencies:
|
||||
gcc-aarch64-linux-gnu: true # conflict with `gcc-multilib`
|
||||
musl-tools: true
|
||||
skip_ssl: true
|
||||
- os: ubuntu-latest
|
||||
target: wasm32-wasip2
|
||||
skip_ssl: true
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-freebsd
|
||||
skip_ssl: true
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
dependencies:
|
||||
gcc-aarch64-linux-gnu: true
|
||||
- os: macos-latest
|
||||
targets:
|
||||
- aarch64-apple-ios
|
||||
- x86_64-apple-darwin
|
||||
target: aarch64-apple-ios
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
prefix-key: v0-rust-${{ join(matrix.targets, '-') }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
# zizmor has an issue with dynamic `with`
|
||||
@@ -148,18 +217,49 @@ jobs:
|
||||
musl-tools: ${{ matrix.dependencies.musl-tools || false }}
|
||||
gcc-aarch64-linux-gnu: ${{ matrix.dependencies.gcc-aarch64-linux-gnu || false }}
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
# key won't match, will rely on restore-keys
|
||||
key: ${{ runner.os }}-${{ matrix.target }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable-${{ matrix.target }}-
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ join(matrix.targets, ',') }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Setup Android NDK
|
||||
if: ${{ contains(matrix.targets, 'aarch64-linux-android') }}
|
||||
if: ${{ matrix.target == 'aarch64-linux-android' }}
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
||||
with:
|
||||
ndk-version: r27
|
||||
add-to-path: true
|
||||
|
||||
- name: Append env conf to cargo
|
||||
if: ${{ matrix.target == 'aarch64-linux-android' }}
|
||||
env:
|
||||
NDK_PATH: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
run: |
|
||||
{
|
||||
echo "[env]"
|
||||
echo "CC_aarch64_linux_android = \"${NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang\""
|
||||
|
||||
echo "AR_aarch64_linux_android = \"${NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\""
|
||||
|
||||
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = \"${NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang\""
|
||||
} >> .cargo/config.toml
|
||||
|
||||
# - name: Prepare repository for redox compilation
|
||||
# run: bash scripts/redox/uncomment-cargo.sh
|
||||
# - name: Check compilation for Redox
|
||||
@@ -168,18 +268,12 @@ jobs:
|
||||
# command: check
|
||||
# args: --ignore-rust-version
|
||||
|
||||
- name: Check compilation
|
||||
run: |
|
||||
for target in ${{ join(matrix.targets, ' ') }}
|
||||
do
|
||||
echo "::group::${target}"
|
||||
cargo check --target $target ${{ env.CARGO_ARGS_NO_SSL }}
|
||||
echo "::endgroup::"
|
||||
done
|
||||
env:
|
||||
CC_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang
|
||||
AR_aarch64_linux_android: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
|
||||
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: ${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang
|
||||
- name: Check compilation with threading
|
||||
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS_NO_SSL }} --features threading
|
||||
|
||||
- name: Check compilation with ssl
|
||||
if: ${{ !matrix.skip_ssl }}
|
||||
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS }}
|
||||
|
||||
snippets_cpython:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||
@@ -188,6 +282,7 @@ jobs:
|
||||
# Tests that can be flaky when running with multiple processes `-j 2`. We will use `-j 1` for these.
|
||||
FLAKY_MP_TESTS: >-
|
||||
test_class
|
||||
test_concurrent_futures
|
||||
test_eintr
|
||||
test_multiprocessing_fork
|
||||
test_multiprocessing_forkserver
|
||||
@@ -200,18 +295,21 @@ jobs:
|
||||
- os: macos-latest
|
||||
extra_test_args:
|
||||
- '-u all'
|
||||
env_polluting_tests: []
|
||||
env_polluting_tests:
|
||||
- test_set
|
||||
skips: []
|
||||
timeout: 50
|
||||
- os: ubuntu-latest
|
||||
extra_test_args:
|
||||
- '-u all'
|
||||
env_polluting_tests: []
|
||||
env_polluting_tests:
|
||||
- test_set
|
||||
skips: []
|
||||
timeout: 60
|
||||
- os: windows-2025
|
||||
extra_test_args: [] # TODO: Enable '-u all'
|
||||
env_polluting_tests: []
|
||||
env_polluting_tests:
|
||||
- test_set
|
||||
skips:
|
||||
- test_rlcompleter
|
||||
- test_pathlib # panic by surrogate chars
|
||||
@@ -226,13 +324,23 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
|
||||
continue-on-error: true
|
||||
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- name: Install macOS dependencies
|
||||
uses: ./.github/actions/install-macos-deps
|
||||
@@ -251,7 +359,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cores=$(python -c 'print(__import__("os").process_cpu_count())')
|
||||
echo "cores=${cores}" >> $GITHUB_OUTPUT
|
||||
echo "cores=${cores}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run CPython tests
|
||||
run: |
|
||||
@@ -262,36 +370,56 @@ jobs:
|
||||
|
||||
- name: Run flaky MP CPython tests
|
||||
run: |
|
||||
target/release/rustpython -m test -j 1 ${{ join(matrix.extra_test_args, ' ') }} --slowest --fail-env-changed --timeout 600 -v ${{ env.FLAKY_MP_TESTS }}
|
||||
for attempt in $(seq 1 5); do
|
||||
echo "::group::Attempt ${attempt}"
|
||||
|
||||
set +e
|
||||
target/release/rustpython -m test -j 1 ${{ join(matrix.extra_test_args, ' ') }} --slowest --fail-env-changed --timeout 600 -v ${{ env.FLAKY_MP_TESTS }}
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
echo "::endgroup::"
|
||||
|
||||
if [ $status -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
exit 1
|
||||
timeout-minutes: ${{ matrix.timeout }}
|
||||
shell: bash
|
||||
env:
|
||||
RUSTPYTHON_SKIP_ENV_POLLUTERS: true
|
||||
|
||||
- name: run cpython tests to check if env polluters have stopped polluting
|
||||
shell: bash
|
||||
run: |
|
||||
for thing in ${{ join(matrix.env_polluting_tests, ' ') }}; do
|
||||
IFS=' ' read -r -a target_array <<< "$TARGETS"
|
||||
|
||||
for thing in "${target_array[@]}"; do
|
||||
for i in $(seq 1 10); do
|
||||
set +e
|
||||
target/release/rustpython -m test -j 1 --slowest --fail-env-changed --timeout 600 -v ${thing}
|
||||
target/release/rustpython -m test -j 1 --slowest --fail-env-changed --timeout 600 -v "${thing}"
|
||||
exit_code=$?
|
||||
set -e
|
||||
if [ ${exit_code} -eq 3 ]; then
|
||||
if [ "${exit_code}" -eq 3 ]; then
|
||||
echo "Test ${thing} polluted the environment on attempt ${i}."
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ ${exit_code} -ne 3 ]; then
|
||||
if [ "${exit_code}" -ne 3 ]; then
|
||||
echo "Test ${thing} is no longer polluting the environment after ${i} attempts!"
|
||||
echo "Please remove ${thing} from matrix.env_polluting_tests in '.github/workflows/ci.yaml'."
|
||||
echo "Please also remove the skip decorators that include the word 'POLLUTERS' in ${thing}."
|
||||
if [ ${exit_code} -ne 0 ]; then
|
||||
if [ "${exit_code}" -ne 0 ]; then
|
||||
echo "Test ${thing} failed with exit code ${exit_code}."
|
||||
echo "Please investigate which test item in ${thing} is failing and either mark it as an expected failure or a skip."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
env:
|
||||
TARGETS: ${{ join(matrix.env_polluting_tests, ' ') }}
|
||||
timeout-minutes: 15
|
||||
|
||||
- if: runner.os != 'Windows'
|
||||
@@ -316,64 +444,160 @@ jobs:
|
||||
shell: bash
|
||||
run: python -I scripts/whats_left.py ${{ env.CARGO_ARGS }} --features jit
|
||||
|
||||
lint:
|
||||
name: Lint Rust & Python code
|
||||
runs-on: ubuntu-latest
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs:
|
||||
- determine_changes
|
||||
permissions:
|
||||
contents: read
|
||||
if: |
|
||||
needs.determine_changes.outputs.rust_code == 'true' ||
|
||||
github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-latest
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Check for redundant test patches
|
||||
run: python scripts/check_redundant_patches.py
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: run clippy on wasm
|
||||
run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings
|
||||
|
||||
- name: Ensure docs generate no warnings
|
||||
run: cargo doc --locked
|
||||
|
||||
- name: Ensure Lib/_opcode_metadata is updated
|
||||
run: |
|
||||
python scripts/generate_opcode_metadata.py
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install ruff
|
||||
uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3.6.1
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
version: "0.15.5"
|
||||
args: "--version"
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
|
||||
continue-on-error: true
|
||||
|
||||
- run: ruff check --diff
|
||||
- name: Clippy
|
||||
run: cargo clippy --keep-going ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings
|
||||
|
||||
- run: ruff format --check
|
||||
|
||||
- name: install prettier
|
||||
run: |
|
||||
yarn global add prettier
|
||||
yarn global bin >> "$GITHUB_PATH"
|
||||
|
||||
- name: check wasm code with prettier
|
||||
# prettier doesn't handle ignore files very well: https://github.com/prettier/prettier/issues/8506
|
||||
run: cd wasm && git ls-files -z | xargs -0 prettier --check -u
|
||||
# Keep cspell check as the last step. This is optional test.
|
||||
- name: install extra dictionaries
|
||||
run: npm install @cspell/dict-en_us @cspell/dict-cpp @cspell/dict-python @cspell/dict-rust @cspell/dict-win32 @cspell/dict-shell
|
||||
- name: spell checker
|
||||
uses: streetsidesoftware/cspell-action@v8
|
||||
cargo_shear:
|
||||
name: cargo shear
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- determine_changes
|
||||
permissions:
|
||||
contents: read
|
||||
if: |
|
||||
needs.determine_changes.outputs.rust_code == 'true' ||
|
||||
github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
files: "**/*.rs"
|
||||
incremental_files_only: true
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: cargo-bins/cargo-binstall@aaa84a43aec4955a42c5ffc65d258961e39f276e # v1.19.1
|
||||
|
||||
- name: cargo shear
|
||||
run: |
|
||||
cargo binstall --no-confirm cargo-shear
|
||||
cargo shear
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
security-events: write # for zizmor
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: actionlint
|
||||
uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0
|
||||
|
||||
- name: zizmor
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
|
||||
- name: restore prek cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
path: ~/.cache/prek
|
||||
|
||||
# TODO: Remove on 2026/06/02 when node24 is the default
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
package-manager-cache: false
|
||||
node-version: "24"
|
||||
|
||||
- name: install prek
|
||||
id: prek
|
||||
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
||||
with:
|
||||
cache: false
|
||||
show-verbose-logs: false
|
||||
install-only: true
|
||||
|
||||
- name: prek run
|
||||
run: prek run --show-diff-on-failure --color=always --all-files
|
||||
|
||||
- name: Get target CPython version
|
||||
id: cpython-version
|
||||
run: |
|
||||
version=$(cat .python-version)
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone CPython
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: python/cpython
|
||||
path: cpython
|
||||
ref: "v${{ steps.cpython-version.outputs.version }}"
|
||||
persist-credentials: false
|
||||
|
||||
- name: prek run (manual stage)
|
||||
run: prek run --show-diff-on-failure --color=always --all-files --hook-stage manual
|
||||
env:
|
||||
CPYTHON_ROOT: ${{ github.workspace }}/cpython
|
||||
|
||||
- name: save prek cache
|
||||
if: ${{ github.ref == 'refs/heads/main' }} # only save on main
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
path: ~/.cache/prek
|
||||
|
||||
- name: restore git permissions
|
||||
if: ${{ !cancelled() }}
|
||||
run: sudo chown -R "$(id -u):$(id -g)" .git
|
||||
|
||||
- name: reviewdog
|
||||
if: ${{ !cancelled() }}
|
||||
uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # v1.24.0
|
||||
with:
|
||||
level: warning
|
||||
fail_level: error
|
||||
|
||||
miri:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||
@@ -392,9 +616,18 @@ jobs:
|
||||
toolchain: ${{ env.NIGHTLY_CHANNEL }}
|
||||
components: miri
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Run tests under miri
|
||||
run: cargo +${{ env.NIGHTLY_CHANNEL }} miri test -p rustpython-vm -- miri_test
|
||||
@@ -414,10 +647,26 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
components: clippy
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --keep-going --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings
|
||||
|
||||
- name: install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
@@ -426,15 +675,31 @@ jobs:
|
||||
wget https://github.com/mozilla/geckodriver/releases/download/v0.36.0/geckodriver-v0.36.0-linux64.tar.gz
|
||||
mkdir geckodriver
|
||||
tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- run: python -m pip install -r requirements.txt
|
||||
working-directory: ./wasm/tests
|
||||
- uses: actions/setup-node@v6
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "wasm/demo/package-lock.json"
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache-dir
|
||||
shell: bash
|
||||
run: echo "dir=$(npm config get cache)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore npm cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
# don't restore on main or release
|
||||
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/release'
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||
key: node-${{ runner.os }}-wasm-demo-
|
||||
restore-keys: |
|
||||
node-${{ runner.os }}-wasm-demo-
|
||||
|
||||
- name: run test
|
||||
run: |
|
||||
driver_path="$(pwd)/../../geckodriver"
|
||||
@@ -444,8 +709,11 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
working-directory: ./wasm/demo
|
||||
- uses: mwilliamson/setup-wabt-action@v3
|
||||
with: { wabt-version: "1.0.36" }
|
||||
|
||||
- uses: mwilliamson/setup-wabt-action@427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2 # v4.0.0
|
||||
with:
|
||||
wabt-version: "1.0.36"
|
||||
|
||||
- name: check wasm32-unknown without js
|
||||
run: |
|
||||
cd example_projects/wasm32_without_js/rustpython-without-js
|
||||
@@ -455,6 +723,7 @@ jobs:
|
||||
echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2
|
||||
fi
|
||||
cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm
|
||||
|
||||
- name: build notebook demo
|
||||
if: github.ref == 'refs/heads/release'
|
||||
run: |
|
||||
@@ -464,15 +733,24 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
working-directory: ./wasm/notebook
|
||||
|
||||
- name: Deploy demo to Github Pages
|
||||
if: success() && github.ref == 'refs/heads/release'
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||
env:
|
||||
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||
PUBLISH_DIR: ./wasm/demo/dist
|
||||
EXTERNAL_REPOSITORY: RustPython/demo
|
||||
PUBLISH_BRANCH: master
|
||||
|
||||
- name: Save npm cache
|
||||
# Save only on main or release
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||
key: node-${{ runner.os }}-wasm-demo-${{ hashFiles('wasm/demo/package-lock.json') }}
|
||||
|
||||
wasm-wasi:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||
name: Run snippets and cpython tests on wasm-wasi
|
||||
@@ -487,12 +765,24 @@ jobs:
|
||||
with:
|
||||
target: wasm32-wasip1
|
||||
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable-wasm32-wasip1-${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable-wasm32-wasip1-
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
|
||||
- name: Setup Wasmer
|
||||
uses: wasmerio/setup-wasmer@v3
|
||||
uses: wasmerio/setup-wasmer@24b15c95293d23f89c68bd40dac76338f773e924 # v3.1
|
||||
|
||||
- name: Install clang
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
@@ -500,34 +790,44 @@ jobs:
|
||||
clang: true
|
||||
|
||||
- name: build rustpython
|
||||
run: cargo build --release --target wasm32-wasip1 --features freeze-stdlib,stdlib --verbose
|
||||
run: cargo build --release --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib,stdio,importlib,host_env --verbose
|
||||
- name: run snippets
|
||||
run: wasmer run --dir $(pwd) target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/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"
|
||||
run: wasmer run --dir "$(pwd)" target/wasm32-wasip1/release/rustpython.wasm -- "$(pwd)/Lib/test/test_int.py"
|
||||
|
||||
cargo-shear:
|
||||
name: cargo shear
|
||||
cargo_doc:
|
||||
needs:
|
||||
- determine_changes
|
||||
if: |
|
||||
(
|
||||
!contains(github.event.pull_request.labels.*.name, 'skip:ci') &&
|
||||
needs.determine_changes.outputs.rust_code == 'true'
|
||||
) || github.ref == 'refs/heads/main'
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
name: cargo doc
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: cargo-bins/cargo-binstall@1800853f2578f8c34492ec76154caef8e163fbca # v1.17.7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
|
||||
- run: cargo shear
|
||||
|
||||
security-lint:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
persist-credentials: false
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stable--${{ hashFiles('**/Cargo.toml') }}-
|
||||
${{ runner.os }}-stable--
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
- name: cargo doc
|
||||
run: cargo doc --locked
|
||||
|
||||
4
.github/workflows/comment-commands.yml
vendored
4
.github/workflows/comment-commands.yml
vendored
@@ -18,4 +18,6 @@ jobs:
|
||||
steps:
|
||||
# Using REST API and not `gh issue edit`. https://github.com/cli/cli/issues/6235#issuecomment-1243487651
|
||||
- run: |
|
||||
curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ github.event.comment.user.login }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
|
||||
curl -H "Authorization: token ${{ github.token }}" -d '{"assignees": ["${{ env.USER }}"]}' https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
|
||||
env:
|
||||
USER: ${{ github.event.comment.user.login }}
|
||||
|
||||
64
.github/workflows/cron-ci.yaml
vendored
64
.github/workflows/cron-ci.yaml
vendored
@@ -1,3 +1,5 @@
|
||||
name: Periodic checks/tasks
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * 6"
|
||||
@@ -5,15 +7,15 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/cron-ci.yaml
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/cron-ci.yaml
|
||||
|
||||
name: Periodic checks/tasks
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit
|
||||
PYTHON_VERSION: "3.14.3"
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' # TODO: Remove on 2026/06/02
|
||||
|
||||
jobs:
|
||||
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
||||
@@ -29,26 +31,34 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
|
||||
- uses: taiki-e/install-action@b550161ef8a7bc4f2a671c0b03a18ac9ccedea1e # v2.79.1
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||
|
||||
- name: Run cargo-llvm-cov with Rust tests.
|
||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit
|
||||
run: cargo llvm-cov --no-report --workspace --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher --verbose --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||
|
||||
- name: Run cargo-llvm-cov with Python snippets.
|
||||
run: python scripts/cargo-llvm-cov.py
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run cargo-llvm-cov with Python test suite.
|
||||
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
|
||||
continue-on-error: true
|
||||
|
||||
- name: Prepare code coverage data
|
||||
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
|
||||
|
||||
- name: Upload to Codecov
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
file: ./codecov.lcov
|
||||
files: ./codecov.lcov
|
||||
|
||||
testdata:
|
||||
name: Collect regression test data
|
||||
@@ -61,12 +71,15 @@ jobs:
|
||||
persist-credentials: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: build rustpython
|
||||
run: cargo build --release --verbose
|
||||
|
||||
- name: collect tests data
|
||||
run: cargo run --release extra_tests/jsontests.py
|
||||
env:
|
||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
||||
|
||||
- name: upload tests data to the website
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
@@ -96,17 +109,19 @@ jobs:
|
||||
persist-credentials: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- name: build rustpython
|
||||
run: cargo build --release --verbose
|
||||
|
||||
- name: Collect what is left data
|
||||
run: |
|
||||
chmod +x ./scripts/whats_left.py
|
||||
./scripts/whats_left.py --features "ssl,sqlite" > whats_left.temp
|
||||
env:
|
||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
||||
|
||||
- name: Upload data to the website
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
@@ -157,28 +172,33 @@ jobs:
|
||||
persist-credentials: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- run: cargo install cargo-criterion
|
||||
|
||||
- name: build benchmarks
|
||||
run: cargo build --release --benches
|
||||
|
||||
- name: collect execution benchmark data
|
||||
run: cargo criterion --bench execution
|
||||
|
||||
- name: collect microbenchmarks data
|
||||
run: cargo criterion --bench microbenchmarks
|
||||
|
||||
- name: restructure generated files
|
||||
run: |
|
||||
cd ./target/criterion/reports
|
||||
find -type d -name cpython | xargs rm -rf
|
||||
find -type d -name rustpython | xargs rm -rf
|
||||
find -mindepth 2 -maxdepth 2 -name violin.svg | xargs rm -rf
|
||||
find -type f -not -name violin.svg | xargs rm -rf
|
||||
for file in $(find -type f -name violin.svg); do mv $file $(echo $file | sed -E "s_\./([^/]+)/([^/]+)/violin\.svg_./\1/\2.svg_"); done
|
||||
find -mindepth 2 -maxdepth 2 -type d | xargs rm -rf
|
||||
find . -type d -name cpython -print0 | xargs -0 rm -rf
|
||||
find . -type d -name rustpython -print0 | xargs -0 rm -rf
|
||||
find . -mindepth 2 -maxdepth 2 -name violin.svg -print0 | xargs -0 rm -rf
|
||||
find . -type f -not -name violin.svg -print0 | xargs -0 rm -rf
|
||||
find . -type f -name violin.svg -exec sh -c 'for file; do mv "$file" "$(echo "$file" | sed -E "s_\./([^/]+)/([^/]+)/violin\.svg_./\1/\2.svg_")"; done' _ {} +
|
||||
find . -mindepth 2 -maxdepth 2 -type d -print0 | xargs -0 rm -rf
|
||||
cd ..
|
||||
mv reports/* .
|
||||
rmdir reports
|
||||
|
||||
- name: upload benchmark data to the website
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
env:
|
||||
|
||||
113
.github/workflows/lib-deps-check.yaml
vendored
113
.github/workflows/lib-deps-check.yaml
vendored
@@ -7,12 +7,9 @@ on:
|
||||
- "Lib/**"
|
||||
|
||||
concurrency:
|
||||
group: lib-deps-${{ github.event.pull_request.number }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.14.3"
|
||||
|
||||
jobs:
|
||||
check_deps:
|
||||
permissions:
|
||||
@@ -37,69 +34,87 @@ jobs:
|
||||
# Checkout only Lib/ directory from PR head for accurate comparison
|
||||
git checkout ${{ github.event.pull_request.head.sha }} -- Lib/
|
||||
|
||||
- name: Checkout CPython
|
||||
- name: Get target CPython version
|
||||
id: cpython-version
|
||||
run: |
|
||||
git clone --depth 1 --branch "v${{ env.PYTHON_VERSION }}" https://github.com/python/cpython.git cpython
|
||||
version=$(cat .python-version)
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout CPython
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: python/cpython
|
||||
path: cpython
|
||||
ref: "v${{ steps.cpython-version.outputs.version }}"
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get changed Lib files
|
||||
id: changed-files
|
||||
id: all-changed-files
|
||||
run: |
|
||||
# Get the list of changed files under Lib/
|
||||
changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py' | head -50)
|
||||
echo "Changed files:"
|
||||
echo "$changed"
|
||||
{
|
||||
echo 'changed<<EOF'
|
||||
|
||||
# Extract unique module names
|
||||
modules=""
|
||||
for file in $changed; do
|
||||
if [[ "$file" == Lib/test/* ]]; then
|
||||
# Test files: Lib/test/test_pydoc.py -> test_pydoc, Lib/test/test_pydoc/foo.py -> test_pydoc
|
||||
module=$(echo "$file" | sed -E 's|^Lib/test/||; s|\.py$||; s|/.*||')
|
||||
# Skip non-test files in test/ (e.g., support.py, __init__.py)
|
||||
if [[ ! "$module" == test_* ]]; then
|
||||
git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py'
|
||||
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Parse changed files
|
||||
id: changed-files
|
||||
run: |
|
||||
from os import environ
|
||||
|
||||
files = environ["FILES"]
|
||||
modules = set()
|
||||
for file in files.splitlines():
|
||||
file = file.strip()
|
||||
|
||||
if file.startswith("Lib/test/"):
|
||||
# Test files:
|
||||
# Lib/test/test_pydoc.py -> test_pydoc
|
||||
# Lib/test/test_pydoc/foo.py -> test_pydoc
|
||||
module = file.removeprefix("Lib/test/").split("/")[0]
|
||||
if not module.startswith("test_"):
|
||||
continue
|
||||
fi
|
||||
else
|
||||
# Lib files: Lib/foo.py -> foo, Lib/foo/__init__.py -> foo
|
||||
module=$(echo "$file" | sed -E 's|^Lib/||; s|/__init__\.py$||; s|\.py$||; s|/.*||')
|
||||
fi
|
||||
if [[ -n "$module" && ! " $modules " =~ " $module " ]]; then
|
||||
modules="$modules $module"
|
||||
fi
|
||||
done
|
||||
else:
|
||||
# Lib files:
|
||||
# Lib/foo.py -> foo
|
||||
# Lib/foo/__init__.py -> foo
|
||||
module = file.removeprefix("Lib/").split("/")[0]
|
||||
|
||||
module = module.split(".")[0]
|
||||
modules.add(module)
|
||||
|
||||
modules=$(echo "$modules" | xargs) # trim whitespace
|
||||
echo "Detected modules: $modules"
|
||||
echo "modules=$modules" >> $GITHUB_OUTPUT
|
||||
print(f"{modules=}")
|
||||
output = " ".join(sorted(modules))
|
||||
output_file = environ["GITHUB_OUTPUT"]
|
||||
with open(output_file, mode="a", encoding="utf-8") as fd:
|
||||
fd.write(f"modules={output}\n")
|
||||
env:
|
||||
FILES: ${{ steps.all-changed-files.outputs.changed }}
|
||||
shell: python
|
||||
|
||||
- name: Setup Python
|
||||
if: steps.changed-files.outputs.modules != ''
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
- name: Run deps check
|
||||
if: steps.changed-files.outputs.modules != ''
|
||||
id: deps-check
|
||||
run: |
|
||||
# Run deps for all modules at once
|
||||
python scripts/update_lib deps ${{ steps.changed-files.outputs.modules }} --depth 2 > /tmp/deps_output.txt 2>&1 || true
|
||||
|
||||
# Read output for GitHub Actions
|
||||
echo "deps_output<<EOF" >> $GITHUB_OUTPUT
|
||||
cat /tmp/deps_output.txt >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# Check if there's any meaningful output
|
||||
if [ -s /tmp/deps_output.txt ]; then
|
||||
echo "has_output=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_output=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "deps_output<<EOF" >> "$GITHUB_OUTPUT"
|
||||
output=$(python scripts/update_lib deps "${MODULES}" --depth 2 2>&1 || true)
|
||||
echo "$output" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
MODULES: ${{ steps.changed-files.outputs.modules }}
|
||||
|
||||
- name: Post comment
|
||||
if: steps.deps-check.outputs.has_output == 'true'
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
if: steps.deps-check.outputs.deps_output != ''
|
||||
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
||||
with:
|
||||
header: lib-deps-check
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
@@ -116,7 +131,7 @@ jobs:
|
||||
|
||||
- name: Remove comment if no Lib changes
|
||||
if: steps.changed-files.outputs.modules == ''
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
||||
with:
|
||||
header: lib-deps-check
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
|
||||
74
.github/workflows/pr-format.yaml
vendored
74
.github/workflows/pr-format.yaml
vendored
@@ -1,74 +0,0 @@
|
||||
name: Format Check
|
||||
|
||||
# This workflow triggers when a PR is opened/updated
|
||||
# Posts inline suggestion comments instead of auto-committing
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
|
||||
concurrency:
|
||||
group: format-check-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.14.3"
|
||||
|
||||
jobs:
|
||||
format_check:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: reviewdog/action-actionlint@0d952c597ef8459f634d7145b0b044a9699e5e43 # v1.71.0
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: Run cargo fmt
|
||||
run: cargo fmt --all
|
||||
|
||||
- name: Install ruff
|
||||
uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3.6.1
|
||||
with:
|
||||
version: "0.15.4"
|
||||
args: "--version"
|
||||
|
||||
- name: Run ruff format
|
||||
run: ruff format
|
||||
|
||||
- name: Run ruff check import sorting
|
||||
run: ruff check --select I --fix
|
||||
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Run generate_opcode_metadata.py
|
||||
run: python scripts/generate_opcode_metadata.py
|
||||
|
||||
- name: Check for formatting changes
|
||||
run: |
|
||||
if ! git diff --exit-code; then
|
||||
echo "::error::Formatting changes detected. Please run 'cargo fmt --all', 'ruff format', and 'ruff check --select I --fix' locally."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Post formatting suggestions
|
||||
if: failure()
|
||||
uses: reviewdog/action-suggester@v1
|
||||
with:
|
||||
tool_name: auto-format
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
level: warning
|
||||
filter_mode: diff_context
|
||||
121
.github/workflows/release.yml
vendored
121
.github/workflows/release.yml
vendored
@@ -12,44 +12,44 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,sqlite,ssl
|
||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
|
||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.platform.runner }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- runner: ubuntu-latest
|
||||
include:
|
||||
- os: 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
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
# - runner: macos-latest
|
||||
# target: x86_64-apple-darwin
|
||||
- runner: windows-2025
|
||||
- os: windows-2025
|
||||
target: x86_64-pc-windows-msvc
|
||||
# - runner: windows-2025
|
||||
# target: i686-pc-windows-msvc
|
||||
# - runner: windows-2025
|
||||
# target: aarch64-pc-windows-msvc
|
||||
# - os: ubuntu-latest
|
||||
# target: i686-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: aarch64-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: armv7-unknown-linux-gnueabi
|
||||
# - os: ubuntu-latest
|
||||
# target: s390x-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: powerpc64le-unknown-linux-gnu
|
||||
# - os: macos-latest
|
||||
# target: x86_64-apple-darwin
|
||||
# - os: windows-2025
|
||||
# target: i686-pc-windows-msvc
|
||||
# - os: windows-2025
|
||||
# target: aarch64-pc-windows-msvc
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -57,39 +57,39 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: cargo-bins/cargo-binstall@main
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Set up Environment
|
||||
shell: bash
|
||||
run: rustup target add ${{ matrix.platform.target }}
|
||||
- name: Set up MacOS Environment
|
||||
run: brew install autoconf automake libtool
|
||||
if: runner.os == 'macOS'
|
||||
- name: Install macOS dependencies
|
||||
uses: ./.github/actions/install-macos-deps
|
||||
with:
|
||||
autoconf: true
|
||||
automake: true
|
||||
libtool: true
|
||||
|
||||
- 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'
|
||||
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls-aws-lc,threading,jit
|
||||
|
||||
- name: Rename Binary
|
||||
run: cp target/${{ matrix.platform.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
|
||||
run: cp target/${{ matrix.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.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
|
||||
run: cp target/${{ matrix.target }}/release/rustpython.exe target/rustpython-release-${{ runner.os }}-${{ matrix.target }}.exe
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
- name: Upload Binary Artifacts
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}
|
||||
path: target/rustpython-release-${{ runner.os }}-${{ matrix.platform.target }}*
|
||||
name: rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
||||
path: target/rustpython-release-${{ runner.os }}-${{ matrix.target }}*
|
||||
|
||||
build-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -106,16 +106,22 @@ jobs:
|
||||
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
|
||||
|
||||
- name: Upload Binary Artifacts
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: rustpython-release-wasm32-wasip1
|
||||
path: target/rustpython-release-wasm32-wasip1.wasm
|
||||
|
||||
- name: install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: mwilliamson/setup-wabt-action@v3
|
||||
with: { wabt-version: "1.0.30" }
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
package-manager-cache: false
|
||||
|
||||
- uses: mwilliamson/setup-wabt-action@427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2 # v4.0.0
|
||||
with:
|
||||
wabt-version: "1.0.30"
|
||||
|
||||
- name: build demo
|
||||
run: |
|
||||
npm install
|
||||
@@ -123,6 +129,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
working-directory: ./wasm/demo
|
||||
|
||||
- name: build notebook demo
|
||||
run: |
|
||||
npm install
|
||||
@@ -131,8 +138,10 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||
working-directory: ./wasm/notebook
|
||||
|
||||
- name: Deploy demo to Github Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
if: ${{ github.repository == 'RustPython/RustPython' }}
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||
with:
|
||||
deploy_key: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||
publish_dir: ./wasm/demo/dist
|
||||
@@ -144,32 +153,29 @@ jobs:
|
||||
# Disable this scheduled job when running on a fork.
|
||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||
needs: [build, build-wasm]
|
||||
permissions:
|
||||
contents: write # for creating a release
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download Binary Artifacts
|
||||
uses: actions/download-artifact@v8.0.1
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: bin
|
||||
pattern: rustpython-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Lib Archive
|
||||
run: |
|
||||
zip -r bin/rustpython-lib.zip Lib/
|
||||
run: zip -r bin/rustpython-lib.zip Lib/
|
||||
|
||||
- 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 }}
|
||||
PRE_RELEASE_INPUT: ${{ github.event.inputs.pre-release }}
|
||||
run: |
|
||||
if [[ "${PRE_RELEASE_INPUT}" == "false" ]]; then
|
||||
RELEASE_TYPE_NAME=Release
|
||||
@@ -188,3 +194,8 @@ jobs:
|
||||
--generate-notes \
|
||||
$PRERELEASE_ARG \
|
||||
bin/rustpython-release-*
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
tag: ${{ github.ref_name }}
|
||||
run: ${{ github.run_number }}
|
||||
PRE_RELEASE_INPUT: ${{ github.event.inputs.pre-release }}
|
||||
|
||||
77
.github/workflows/update-caches.yml
vendored
Normal file
77
.github/workflows/update-caches.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Update Actions Caches
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
CARGO_PROFILE_RELEASE_DEBUG: 0
|
||||
CARGO_ARGS: --workspace --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,ssl-rustls-aws-lc,host_env,threading,jit --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
|
||||
|
||||
jobs:
|
||||
build-caches:
|
||||
name: Build Caches
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
toolchain: stable
|
||||
target: ""
|
||||
- os: ubuntu-latest
|
||||
toolchain: stable
|
||||
target: ""
|
||||
- os: windows-latest
|
||||
toolchain: stable
|
||||
target: ""
|
||||
steps:
|
||||
- name: Checkout RustPython main branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: RustPython/RustPython
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install macos dependencies
|
||||
uses: ./.github/actions/install-macos-deps
|
||||
with:
|
||||
openssl: true
|
||||
|
||||
- name: Build dev cache # dev profile used by check & doc
|
||||
run: cargo build --profile dev ${{ env.CARGO_ARGS }}
|
||||
|
||||
- name: Build test cache
|
||||
run: cargo build --profile test ${{ env.CARGO_ARGS }}
|
||||
|
||||
- name: Build release cache
|
||||
run: cargo build --profile release ${{ env.CARGO_ARGS }}
|
||||
|
||||
- name: Save cache
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-${{ matrix.toolchain }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('Cargo.lock') }}-${{ github.sha }}
|
||||
39
.github/workflows/update-doc-db.yml
vendored
39
.github/workflows/update-doc-db.yml
vendored
@@ -1,8 +1,6 @@
|
||||
name: Update doc DB
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -22,6 +20,8 @@ defaults:
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Generate docs
|
||||
run: python crates/doc/generate.py
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: doc-db-${{ inputs.python-version }}-${{ matrix.os }}
|
||||
path: "crates/doc/generated/*.json"
|
||||
@@ -54,17 +54,19 @@ jobs:
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs: generate
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ inputs.base-ref }}
|
||||
token: ${{ secrets.AUTO_COMMIT_PAT }}
|
||||
|
||||
- name: Create update branch
|
||||
run: git switch -c "update-doc-${PYTHON_VERSION}"
|
||||
env:
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
run: git switch -c "update-doc-${PYTHON_VERSION}"
|
||||
|
||||
- name: Download generated doc DBs
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
@@ -85,19 +87,18 @@ jobs:
|
||||
|
||||
OUTPUT_FILE='crates/doc/src/data.inc.rs'
|
||||
|
||||
echo -n '' > $OUTPUT_FILE
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
echo '// This file was auto-generated by `.github/workflows/update-doc-db.yml`.'
|
||||
echo "// CPython version: ${PYTHON_VERSION}"
|
||||
echo '// spell-checker: disable'
|
||||
echo ''
|
||||
echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {"
|
||||
cat crates/doc/generated/raw_entries.txt
|
||||
echo '};'
|
||||
} > "$OUTPUT_FILE"
|
||||
|
||||
echo '// This file was auto-generated by `.github/workflows/update-doc-db.yml`.' >> $OUTPUT_FILE
|
||||
echo "// CPython version: ${PYTHON_VERSION}" >> $OUTPUT_FILE
|
||||
echo '// spell-checker: disable' >> $OUTPUT_FILE
|
||||
|
||||
echo '' >> $OUTPUT_FILE
|
||||
|
||||
echo "pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! {" >> $OUTPUT_FILE
|
||||
cat crates/doc/generated/raw_entries.txt >> $OUTPUT_FILE
|
||||
echo '};' >> $OUTPUT_FILE
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: doc-db-${{ inputs.python-version }}
|
||||
path: "crates/doc/src/data.inc.rs"
|
||||
@@ -107,7 +108,7 @@ jobs:
|
||||
|
||||
- name: Commit, push and create PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.AUTO_COMMIT_PAT }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
BASE_REF: ${{ inputs.base-ref }}
|
||||
run: |
|
||||
|
||||
16
.github/workflows/update-libs-status.yaml
vendored
16
.github/workflows/update-libs-status.yaml
vendored
@@ -13,7 +13,6 @@ permissions:
|
||||
issues: write
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "v3.14.3"
|
||||
ISSUE_ID: "6839"
|
||||
|
||||
jobs:
|
||||
@@ -29,13 +28,20 @@ jobs:
|
||||
sparse-checkout: |-
|
||||
Lib
|
||||
scripts/update_lib
|
||||
.python-version
|
||||
|
||||
- name: Clone CPython ${{ env.PYTHON_VERSION }}
|
||||
- name: Get target CPython version
|
||||
id: cpython-version
|
||||
run: |
|
||||
version=$(cat rustpython/.python-version)
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone CPython
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: python/cpython
|
||||
path: cpython
|
||||
ref: ${{ env.PYTHON_VERSION }}
|
||||
ref: "v${{ steps.cpython-version.outputs.version }}"
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
Lib
|
||||
@@ -56,14 +62,14 @@ jobs:
|
||||
|
||||
## Summary
|
||||
|
||||
Check \`scripts/update_lib\` for tools. As a note, the current latest Python version is \`${{ env.PYTHON_VERSION }}\`.
|
||||
Check \`scripts/update_lib\` for tools. As a note, the current latest Python version is \`${{ steps.cpython-version.outputs.version }}\`.
|
||||
|
||||
Previous versions' issues as reference
|
||||
- 3.13: #5529
|
||||
|
||||
<!--
|
||||
Quick guideline for Copilot:
|
||||
# Clone \`github.com/python/cpython\` \`${{ env.PYTHON_VERSION }}\` tag under RustPython working dir with depth 1 option; never 3.14.0 or 3.14.1 or 3.14.2
|
||||
# Clone \`github.com/python/cpython\` \`${{ steps.cpython-version.outputs.version }}\` tag under RustPython working dir with depth 1 option; never 3.14.0 or 3.14.1 or 3.14.2
|
||||
# Pick a library or test to update. Probably user give one.
|
||||
# Run \`python3 scripts/update_lib quick <name>\`
|
||||
# A commit is automatically created. push the commit.
|
||||
|
||||
76
.github/workflows/upgrade-pylib.lock.yml
generated
vendored
76
.github/workflows/upgrade-pylib.lock.yml
generated
vendored
@@ -58,11 +58,11 @@ jobs:
|
||||
comment_repo: ""
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@08a903b1fb2e493a84a57577778fe5dd711f9468 # v0.58.3
|
||||
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Check workflow file timestamps
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_WORKFLOW_FILE: "upgrade-pylib.lock.yml"
|
||||
with:
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@08a903b1fb2e493a84a57577778fe5dd711f9468 # v0.58.3
|
||||
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Checkout repository
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
|
||||
# Cache configuration from frontmatter processed below
|
||||
- name: Cache (cpython-lib-${{ env.PYTHON_VERSION }})
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
key: cpython-lib-${{ env.PYTHON_VERSION }}
|
||||
path: cpython
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
id: checkout-pr
|
||||
if: |
|
||||
github.event.pull_request
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
await main();
|
||||
- name: Generate agentic run info
|
||||
id: generate_aw_info
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.16.4
|
||||
- name: Determine automatic lockdown mode for GitHub MCP server
|
||||
id: determine-automatic-lockdown
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
||||
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
||||
@@ -484,7 +484,7 @@ jobs:
|
||||
}
|
||||
GH_AW_MCP_CONFIG_EOF
|
||||
- name: Generate workflow overview
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
|
||||
@@ -508,10 +508,11 @@ jobs:
|
||||
cat << 'GH_AW_PROMPT_EOF' > "$GH_AW_PROMPT"
|
||||
<system>
|
||||
GH_AW_PROMPT_EOF
|
||||
cat "/opt/gh-aw/prompts/xpia.md" >> "$GH_AW_PROMPT"
|
||||
cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
|
||||
cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
|
||||
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
|
||||
{
|
||||
cat "/opt/gh-aw/prompts/xpia.md"
|
||||
cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
|
||||
cat "/opt/gh-aw/prompts/markdown.md"
|
||||
cat << 'GH_AW_PROMPT_EOF'
|
||||
<safe-outputs>
|
||||
<description>GitHub API Access Instructions</description>
|
||||
<important>
|
||||
@@ -569,14 +570,15 @@ jobs:
|
||||
</github-context>
|
||||
|
||||
GH_AW_PROMPT_EOF
|
||||
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
|
||||
cat << 'GH_AW_PROMPT_EOF'
|
||||
</system>
|
||||
GH_AW_PROMPT_EOF
|
||||
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
|
||||
cat << 'GH_AW_PROMPT_EOF'
|
||||
{{#runtime-import .github/workflows/upgrade-pylib.md}}
|
||||
GH_AW_PROMPT_EOF
|
||||
} >> "$GH_AW_PROMPT"
|
||||
- name: Substitute placeholders
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
|
||||
@@ -610,7 +612,7 @@ jobs:
|
||||
}
|
||||
});
|
||||
- name: Interpolate variables and render templates
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
|
||||
@@ -690,7 +692,7 @@ jobs:
|
||||
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
|
||||
- name: Redact secrets in logs
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -705,14 +707,14 @@ jobs:
|
||||
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload Safe Outputs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: safe-output
|
||||
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||
if-no-files-found: warn
|
||||
- name: Ingest agent output
|
||||
id: collect_output
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,sh.rustup.rs,static.crates.io,static.rust-lang.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
|
||||
@@ -726,13 +728,13 @@ jobs:
|
||||
await main();
|
||||
- name: Upload sanitized agent output
|
||||
if: always() && env.GH_AW_AGENT_OUTPUT
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: agent-output
|
||||
path: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
if-no-files-found: warn
|
||||
- name: Upload engine output files
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: agent_outputs
|
||||
path: |
|
||||
@@ -741,7 +743,7 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
- name: Parse agent logs for step summary
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
|
||||
with:
|
||||
@@ -752,7 +754,7 @@ jobs:
|
||||
await main();
|
||||
- name: Parse MCP gateway logs for step summary
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -772,7 +774,7 @@ jobs:
|
||||
- name: Upload agent artifacts
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: agent-artifacts
|
||||
path: |
|
||||
@@ -804,7 +806,7 @@ jobs:
|
||||
total_count: ${{ steps.missing_tool.outputs.total_count }}
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@08a903b1fb2e493a84a57577778fe5dd711f9468 # v0.58.3
|
||||
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Download agent output artifact
|
||||
@@ -820,7 +822,7 @@ jobs:
|
||||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
|
||||
- name: Process No-Op Messages
|
||||
id: noop
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_NOOP_MAX: 1
|
||||
@@ -834,7 +836,7 @@ jobs:
|
||||
await main();
|
||||
- name: Record Missing Tool
|
||||
id: missing_tool
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||
@@ -847,7 +849,7 @@ jobs:
|
||||
await main();
|
||||
- name: Handle Agent Failure
|
||||
id: handle_agent_failure
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||
@@ -865,7 +867,7 @@ jobs:
|
||||
await main();
|
||||
- name: Handle No-Op Message
|
||||
id: handle_noop_message
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||
@@ -882,7 +884,7 @@ jobs:
|
||||
await main();
|
||||
- name: Handle Create Pull Request Error
|
||||
id: handle_create_pr_error
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||
@@ -896,7 +898,7 @@ jobs:
|
||||
await main();
|
||||
- name: Update reaction comment with completion status
|
||||
id: conclusion
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
|
||||
@@ -925,7 +927,7 @@ jobs:
|
||||
success: ${{ steps.parse_results.outputs.success }}
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@08a903b1fb2e493a84a57577778fe5dd711f9468 # v0.58.3
|
||||
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Download agent artifacts
|
||||
@@ -946,7 +948,7 @@ jobs:
|
||||
run: |
|
||||
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
|
||||
- name: Setup threat detection
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
WORKFLOW_NAME: "Upgrade Python Library"
|
||||
WORKFLOW_DESCRIPTION: "Pick an out-of-sync Python library from the todo list and upgrade it\nby running `scripts/update_lib quick`, then open a pull request."
|
||||
@@ -999,7 +1001,7 @@ jobs:
|
||||
XDG_CONFIG_HOME: /home/runner
|
||||
- name: Parse threat detection results
|
||||
id: parse_results
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -1008,7 +1010,7 @@ jobs:
|
||||
await main();
|
||||
- name: Upload threat detection log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: threat-detection.log
|
||||
path: /tmp/gh-aw/threat-detection/detection.log
|
||||
@@ -1037,7 +1039,7 @@ jobs:
|
||||
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
|
||||
steps:
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@08a903b1fb2e493a84a57577778fe5dd711f9468 # v0.58.3
|
||||
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Download agent output artifact
|
||||
@@ -1079,7 +1081,7 @@ jobs:
|
||||
echo "Git configured with standard GitHub Actions identity"
|
||||
- name: Process Safe Outputs
|
||||
id: process_safe_outputs
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ github.ref_name }}\",\"draft\":false,\"expires\":30,\"labels\":[\"pylib-sync\"],\"max\":1,\"max_patch_size\":1024,\"title_prefix\":\"Update \"},\"missing_data\":{},\"missing_tool\":{}}"
|
||||
|
||||
2
.github/workflows/upgrade-pylib.md
vendored
2
.github/workflows/upgrade-pylib.md
vendored
@@ -52,7 +52,7 @@ cache:
|
||||
- cpython-lib-
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "v3.14.3"
|
||||
PYTHON_VERSION: "v3.14.4"
|
||||
ISSUE_ID: "6839"
|
||||
---
|
||||
|
||||
|
||||
14
.github/zizmor.yml
vendored
Normal file
14
.github/zizmor.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
# dtolnay/rust-toolchain is a trusted action that uses lightweight branch
|
||||
# refs (@stable, @nightly, etc.) by design. Pinning to a hash would break
|
||||
# the intended usage pattern.
|
||||
# We can remove this once https://github.com/dtolnay/rust-toolchain/issues/180 is resolved
|
||||
dtolnay/rust-toolchain: any
|
||||
# dtolnay/rust-toolchain handles component installation, target addition, and
|
||||
# override configuration beyond what a bare `rustup` invocation provides.
|
||||
# See: https://github.com/zizmorcore/zizmor/issues/1817
|
||||
superfluous-actions:
|
||||
disable: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,7 +10,6 @@ __pycache__/
|
||||
wasm-pack.log
|
||||
.idea/
|
||||
.envrc
|
||||
.python-version
|
||||
|
||||
flame-graph.html
|
||||
flame.txt
|
||||
@@ -28,4 +27,4 @@ Lib/site-packages/*
|
||||
Lib/test/data/*
|
||||
!Lib/test/data/README
|
||||
cpython/
|
||||
|
||||
.claude/scheduled_tasks.lock
|
||||
84
.pre-commit-config.yaml
Normal file
84
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
# NOTE: Reason for not using `prek.toml` is dependabot supports `pre-commit` as an ecosystem
|
||||
# See: https://github.blog/changelog/2026-03-10-dependabot-now-supports-pre-commit-hooks/
|
||||
|
||||
fail_fast: false
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.12
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
priority: 0
|
||||
|
||||
- id: ruff-check
|
||||
args: [--select, I, --fix, --exit-non-zero-on-fix]
|
||||
types_or: [python]
|
||||
require_serial: true
|
||||
priority: 1
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: redundant-test-patches
|
||||
name: check redundant test patches
|
||||
entry: scripts/check_redundant_patches.py
|
||||
files: '^Lib/test/.*\.py$'
|
||||
language: script
|
||||
types: [python]
|
||||
priority: 0
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rustfmt
|
||||
name: rustfmt
|
||||
entry: rustfmt
|
||||
language: system
|
||||
types: [rust]
|
||||
priority: 0
|
||||
|
||||
- id: generate-rs-opcode-metadata
|
||||
name: generate rust opcode metadata
|
||||
entry: python tools/opcode_metadata/generate_rs_opcode_metadata.py
|
||||
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
|
||||
pass_filenames: false
|
||||
language: system
|
||||
require_serial: true
|
||||
priority: 1 # so rustfmt runs first
|
||||
stages:
|
||||
- manual
|
||||
|
||||
- id: generate-py-opcode-metadata
|
||||
name: generate python opcode metadata
|
||||
entry: python tools/opcode_metadata/generate_py_opcode_metadata.py
|
||||
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
|
||||
pass_filenames: false
|
||||
language: system
|
||||
require_serial: true
|
||||
priority: 1 # so rustfmt runs first
|
||||
stages:
|
||||
- manual
|
||||
|
||||
- repo: https://github.com/streetsidesoftware/cspell-cli
|
||||
rev: v10.0.0
|
||||
hooks:
|
||||
- id: cspell
|
||||
types: [rust]
|
||||
additional_dependencies:
|
||||
- '@cspell/dict-en_us'
|
||||
- '@cspell/dict-cpp'
|
||||
- '@cspell/dict-python'
|
||||
- '@cspell/dict-rust'
|
||||
- '@cspell/dict-win32'
|
||||
- '@cspell/dict-shell'
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.8.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
files: '^wasm/.*$'
|
||||
priority: 0
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.14.5
|
||||
41
AGENTS.md
41
AGENTS.md
@@ -38,6 +38,12 @@ RustPython is a Python 3 interpreter written in Rust, implementing Python 3.14.0
|
||||
- Always ask the user before performing any git operations that affect the remote repository
|
||||
- Commits can be created locally when requested, but pushing and PR creation require explicit approval
|
||||
|
||||
**CRITICAL: Pre-commit Checks**
|
||||
- Before creating ANY commit, you MUST run `prek run --all-files` (or `pre-commit run --all-files`) AND the full test suite. Both must pass — do not commit if either fails.
|
||||
- Test commands are documented in the [Testing](#testing) section below. At minimum run `cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher`; if the change touches `extra_tests/snippets/` run `pytest -v` there too, and if it touches `Lib/` or interpreter behavior, run the relevant `cargo run --release -- -m test <module>` modules.
|
||||
- If a hook auto-fixes files (e.g. `ruff-format`, `rustfmt`), re-stage the fixes, re-run `prek` until it reports a clean pass, then re-run the tests, then commit.
|
||||
- NEVER bypass these checks with `--no-verify`, `--no-gpg-sign`, or by skipping tests "because the change is small". If a hook or test fails, fix the underlying issue and create a new commit — do not amend or force the failing commit through.
|
||||
|
||||
## Important Development Notes
|
||||
|
||||
### Running Python Code
|
||||
@@ -81,6 +87,35 @@ The `Lib/` directory contains Python standard library files copied from the CPyt
|
||||
- `unittest.skip("TODO: RustPython <reason>")`
|
||||
- `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment
|
||||
|
||||
#### Choosing the right marker
|
||||
|
||||
When marking a test that fails on RustPython, prefer one of the following forms:
|
||||
|
||||
```python
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; <reason>
|
||||
# or
|
||||
@unittest.expectedFailureIf(<condition>, "TODO: RUSTPYTHON; <reason>")
|
||||
```
|
||||
|
||||
If the test would crash the interpreter (segfault, Rust panic, abort, infinite loop), use `skip` instead so the rest of the suite can still run:
|
||||
|
||||
```python
|
||||
@unittest.skip("TODO: RUSTPYTHON; <reason>")
|
||||
# or
|
||||
@unittest.skipIf(<condition>, "TODO: RUSTPYTHON; <reason>")
|
||||
```
|
||||
|
||||
**When to use which:**
|
||||
|
||||
- **Prefer `expectedFailure` / `expectedFailureIf`** by default. The test body still runs, so if RustPython is later fixed, the unexpected pass surfaces immediately and the decorator can be removed. Use the conditional `*If` form when the failure is environment-specific (e.g., a platform or build flag).
|
||||
- **Use `skip` / `skipIf` only when running the test would take down the test process** — segfaults, Rust panics, aborts, or hangs that block subsequent tests. Skipping keeps the suite usable; `expectedFailure` cannot help here, because the test body still executes.
|
||||
|
||||
To find WIP entries that are partly modified and may need follow-up:
|
||||
|
||||
```bash
|
||||
grep -d recurse 'TODO: RUSTPYTHON' Lib/test/
|
||||
```
|
||||
|
||||
### Clean Build
|
||||
|
||||
When you modify bytecode instructions, a full clean is required:
|
||||
@@ -129,6 +164,7 @@ Run `./scripts/whats_left.py` to get a list of unimplemented methods, which is h
|
||||
|
||||
- Do not delete or rewrite existing comments unless they are factually wrong or directly contradict the new code.
|
||||
- Do not add decorative section separators (e.g. `// -----------`, `// ===`, `/* *** */`). Use `///` doc-comments or short `//` comments only when they add value.
|
||||
- Do not put `///` doc comments on items annotated with `#[pyattr]`, `#[pyclass]`, or `#[pyfunction]`. The derive macros pull authoritative docstrings from CPython via the `rustpython-doc` crate; a Rust doc comment overrides that source, and on `#[pyattr]` it is silently dropped.
|
||||
|
||||
#### Avoid Duplicate Code in Branches
|
||||
|
||||
@@ -258,9 +294,14 @@ See DEVELOPMENT.md "CPython Version Upgrade Checklist" section.
|
||||
- Document that it requires PEP 695 support
|
||||
- Focus on tests that can be fixed through Rust code changes only
|
||||
|
||||
## CI Workflows
|
||||
|
||||
If you modify any file under `.github/workflows/`, the change must pass a [zizmor](https://docs.zizmor.sh/) scan in CI.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Check the [architecture document](/architecture/architecture.md) for a high-level overview
|
||||
- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions
|
||||
- Generate documentation with `cargo doc --no-deps --all`
|
||||
- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/)
|
||||
- [How to update test files](https://github.com/RustPython/RustPython/wiki/How-to-update-test-files#checkout-cpython-source-code-initial-setup) — guide for syncing test cases from upstream CPython into the `Lib/` directory
|
||||
|
||||
@@ -1,4 +1,28 @@
|
||||
# RustPython Development Guide and Tips
|
||||
# Contributing to RustPython
|
||||
|
||||
Contributions are more than welcome, and in many cases we are happy to guide
|
||||
contributors through PRs or on [**Discord**](https://discord.gg/vru8NypEhv).
|
||||
|
||||
## Finding ways to help
|
||||
|
||||
We label issues that would be good for a first time contributor as [`good first issue`](https://github.com/RustPython/RustPython/issues?q=label%3A%22good+first+issue%22+is%3Aissue+is%3Aopen+).
|
||||
Also checkout the [issue tracker](https://github.com/RustPython/RustPython/issues) for all open issues.
|
||||
|
||||
You can enhance CPython compatibility by increasing our unittest coverage, you can see [This pinned issue](https://github.com/RustPython/RustPython/issues/6839) to see which libs and tests need be updated to our current supported python version.
|
||||
|
||||
Another approach is to checkout the source code: builtin functions and object
|
||||
methods are often the simplest and easiest way to contribute.
|
||||
|
||||
You can also simply run `python -I scripts/whats_left.py` to assist in finding any unimplemented method.
|
||||
|
||||
## Use of AI
|
||||
|
||||
We **require all use of AI in contributions to follow our
|
||||
[AI Policy](https://github.com/RustPython/.github/blob/main/AI_POLICY.md)**.
|
||||
|
||||
If your contribution does not follow the policy, it will be closed.
|
||||
|
||||
## RustPython Development Guide and Tips
|
||||
|
||||
RustPython attracts developers with interest and experience in Rust, Python,
|
||||
or WebAssembly. Whether you are familiar with Rust, Python, or
|
||||
2107
Cargo.lock
generated
2107
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
217
Cargo.toml
217
Cargo.toml
@@ -10,7 +10,8 @@ repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls", "host_env"]
|
||||
capi = ["dep:rustpython-capi", "threading"]
|
||||
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls-aws-lc", "host_env"]
|
||||
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
|
||||
importlib = ["rustpython-vm/importlib"]
|
||||
encodings = ["rustpython-vm/encodings"]
|
||||
@@ -21,30 +22,35 @@ freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/fre
|
||||
jit = ["rustpython-vm/jit"]
|
||||
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
||||
sqlite = ["rustpython-stdlib/sqlite"]
|
||||
ssl = []
|
||||
ssl = ["host_env"]
|
||||
ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
|
||||
ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs"]
|
||||
ssl-rustls-aws-lc-fips = ["ssl-rustls-aws-lc", "rustls/fips"]
|
||||
ssl-openssl = ["ssl", "rustpython-stdlib/ssl-openssl"]
|
||||
ssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-vendor"]
|
||||
ssl-openssl-vendor = ["ssl-openssl", "rustpython-stdlib/ssl-openssl-vendor"]
|
||||
tkinter = ["rustpython-stdlib/tkinter"]
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
|
||||
[dependencies]
|
||||
rustpython-capi = { workspace = true, optional = true }
|
||||
rustpython-compiler = { workspace = true }
|
||||
rustpython-pylib = { workspace = true, optional = true }
|
||||
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
|
||||
rustpython-vm = { workspace = true, features = ["compiler", "gc"] }
|
||||
|
||||
cfg-if = { workspace = true }
|
||||
log = { workspace = true }
|
||||
flame = { workspace = true, optional = true }
|
||||
|
||||
lexopt = "0.3"
|
||||
dirs = { package = "dirs-next", version = "2.0" }
|
||||
dirs = "6"
|
||||
env_logger = "0.11"
|
||||
flamescope = { version = "0.1.2", optional = true }
|
||||
|
||||
rustls = { workspace = true, optional = true }
|
||||
rustls-graviola = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
@@ -53,7 +59,7 @@ rustyline = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
pyo3 = { version = "0.28.2", features = ["auto-initialize"] }
|
||||
pyo3 = { workspace = true, features = ["auto-initialize"] }
|
||||
rustpython-stdlib = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
|
||||
@@ -69,6 +75,17 @@ harness = false
|
||||
name = "rustpython"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom_tls_providers"
|
||||
path = "examples/custom_tls_providers.rs"
|
||||
required-features = [
|
||||
"rustls-graviola",
|
||||
"rustls/ring",
|
||||
"rustpython-pylib/freeze-stdlib",
|
||||
"rustpython-stdlib/ssl-rustls",
|
||||
"rustpython-vm/freeze-stdlib",
|
||||
]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
@@ -136,15 +153,17 @@ exclude = ["pymath"]
|
||||
version = "0.5.0"
|
||||
authors = ["RustPython Team"]
|
||||
edition = "2024"
|
||||
rust-version = "1.93.0"
|
||||
rust-version = "1.95.0"
|
||||
repository = "https://github.com/RustPython/RustPython"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
rustpython-capi = { path = "crates/capi", version = "0.5.0" }
|
||||
rustpython-compiler-core = { path = "crates/compiler-core", version = "0.5.0" }
|
||||
rustpython-compiler = { path = "crates/compiler", version = "0.5.0" }
|
||||
rustpython-codegen = { path = "crates/codegen", version = "0.5.0" }
|
||||
rustpython-common = { path = "crates/common", version = "0.5.0" }
|
||||
rustpython-host_env = { path = "crates/host_env", version = "0.5.0" }
|
||||
rustpython-derive = { path = "crates/derive", version = "0.5.0" }
|
||||
rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" }
|
||||
rustpython-jit = { path = "crates/jit", version = "0.5.0" }
|
||||
@@ -156,77 +175,155 @@ rustpython-sre_engine = { path = "crates/sre_engine", version = "0.5.0" }
|
||||
rustpython-wtf8 = { path = "crates/wtf8", version = "0.5.0" }
|
||||
rustpython-doc = { path = "crates/doc", version = "0.5.0" }
|
||||
|
||||
# Ruff tag 0.15.6 is based on commit e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675
|
||||
# Use RustPython-packaged Ruff crates from the published fork while keeping
|
||||
# existing crate names in the codebase.
|
||||
ruff_python_parser = { package = "rustpython-ruff_python_parser", version = "0.15.8" }
|
||||
ruff_python_ast = { package = "rustpython-ruff_python_ast", version = "0.15.8" }
|
||||
ruff_text_size = { package = "rustpython-ruff_text_size", version = "0.15.8" }
|
||||
ruff_source_file = { package = "rustpython-ruff_source_file", version = "0.15.8" }
|
||||
# To update ruff crates, comment out the above lines and uncomment the following lines to pull directly from the Ruff repository at the specified commit hash.
|
||||
# Ruff tag 0.15.8 is based on commit c2a8815842f9dc5d24ec19385eae0f1a7188b0d9
|
||||
# at the time of this capture. We use the commit hash to ensure reproducible builds.
|
||||
ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
|
||||
ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
|
||||
ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "e4c7f357777a2fdd34dbe6a98b1b7d3e7488f675" }
|
||||
# ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
# ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "c2a8815842f9dc5d24ec19385eae0f1a7188b0d9" }
|
||||
|
||||
der = { version = "0.8", features = ["alloc", "oid", "pem", "zeroize"] }
|
||||
phf = { version = "0.13.1", default-features = false, features = ["macros"]}
|
||||
ahash = "0.8.12"
|
||||
adler32 = "1.2.0"
|
||||
approx = "0.5.1"
|
||||
ascii = "1.1"
|
||||
base64 = "0.22"
|
||||
blake2 = "0.10.4"
|
||||
bitflags = "2.11.0"
|
||||
bitflagset = "0.0.3"
|
||||
bstr = "1"
|
||||
cfg-if = "1.0"
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["clock", "oldtime", "std"] }
|
||||
bzip2 = "0.6"
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["clock", "std"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
constant_time_eq = "0.4"
|
||||
cranelift = "0.131.2"
|
||||
cranelift-jit = "0.131.2"
|
||||
cranelift-module = "0.131.0"
|
||||
crc32fast = "1.3.2"
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
crossbeam-utils = "0.8.21"
|
||||
csv-core = "0.1.11"
|
||||
digest = "0.10.7"
|
||||
dns-lookup = "3.0"
|
||||
dyn-clone = "1.0.10"
|
||||
exitcode = "1.1.2"
|
||||
flame = "0.2.2"
|
||||
flamer = "0.5"
|
||||
flate2 = { version = "1.1.9", default-features = false }
|
||||
# Bump only when the openssl crate bumps it
|
||||
foreign-types-shared = "0.1"
|
||||
gethostname = "1.0.2"
|
||||
getrandom = { version = "0.3", features = ["std"] }
|
||||
glob = "0.3"
|
||||
half = "2"
|
||||
hex = "0.4.3"
|
||||
indexmap = { version = "2.13.0", features = ["std"] }
|
||||
insta = "1.46"
|
||||
hexf-parse = "0.2.1"
|
||||
hmac = "0.12"
|
||||
indexmap = { version = "2.14.0", features = ["std"] }
|
||||
insta = "1.47"
|
||||
itertools = "0.14.0"
|
||||
is-macro = "0.3.7"
|
||||
js-sys = "0.3"
|
||||
junction = "1.4.2"
|
||||
libc = "0.2.183"
|
||||
lexical-parse-float = "1.0.6"
|
||||
libc = "0.2.186"
|
||||
libffi = "5"
|
||||
libloading = "0.9"
|
||||
liblzma = "0.4"
|
||||
liblzma-sys = "0.4"
|
||||
libsqlite3-sys = "0.37"
|
||||
libz-rs-sys = "0.6"
|
||||
lock_api = "0.4"
|
||||
log = "0.4.29"
|
||||
nix = { version = "0.30", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
|
||||
lz4_flex = "0.13"
|
||||
nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
|
||||
mac_address = "1.1.3"
|
||||
malachite-bigint = "0.9.1"
|
||||
malachite-q = "0.9.1"
|
||||
malachite-base = "0.9.1"
|
||||
md-5 = "0.10.1"
|
||||
memchr = "2.8.0"
|
||||
memmap2 = "0.9.10"
|
||||
mt19937 = "<=3.2" # upgrade it once rand is upgraded
|
||||
num-complex = "0.4.6"
|
||||
num-integer = "0.1.46"
|
||||
num-traits = "0.2"
|
||||
num_cpus = "1.17.0"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
oid-registry = "0.8"
|
||||
openssl = "0.10.80"
|
||||
openssl-sys = "0.9.110"
|
||||
openssl-probe = "0.2.1"
|
||||
optional = "0.5"
|
||||
parking_lot = "0.12.3"
|
||||
paste = "1.0.15"
|
||||
pbkdf2 = "0.12"
|
||||
pem-rfc7468 = "1.0"
|
||||
pkcs8 = "0.11"
|
||||
proc-macro2 = "1.0.105"
|
||||
psm = "0.1"
|
||||
pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
|
||||
pyo3 = "0.28"
|
||||
quote = "1.0.45"
|
||||
radium = "1.1.1"
|
||||
rand = "0.9"
|
||||
rand_core = { version = "0.9", features = ["os_rng"] }
|
||||
rustix = { version = "1.1", features = ["event"] }
|
||||
rustyline = "17.0.1"
|
||||
rapidhash = "4.4.1"
|
||||
result-like = "0.5.0"
|
||||
rustix = { version = "1.1", features = ["event", "param", "system"] }
|
||||
rustls = { version = "0.23.39", default-features = false }
|
||||
rustls-graviola = "0.3"
|
||||
rustls-native-certs = "0.8"
|
||||
rustls-pemfile = "2.2"
|
||||
rustls-platform-verifier = "0.7"
|
||||
rustyline = "18"
|
||||
serde = { package = "serde_core", version = "1.0.225", default-features = false, features = ["alloc"] }
|
||||
schannel = "0.1.28"
|
||||
scoped-tls = "1"
|
||||
schannel = "0.1.29"
|
||||
scopeguard = "1"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
sha-1 = "0.10.0"
|
||||
sha2 = "0.10.2"
|
||||
sha3 = "0.10.1"
|
||||
siphasher = "1"
|
||||
socket2 = "0.6.3"
|
||||
static_assertions = "1.1"
|
||||
strum = "0.27"
|
||||
strum = "0.28"
|
||||
strum_macros = "0.28"
|
||||
syn = "2"
|
||||
syn-ext = "0.5.0"
|
||||
system-configuration = "0.7.0"
|
||||
tcl-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
|
||||
textwrap = { version = "0.16.2", default-features = false }
|
||||
termios = "0.3.3"
|
||||
thiserror = "2.0"
|
||||
unicode-casing = "0.1.1"
|
||||
unic-char-property = "0.9.0"
|
||||
unic-normal = "0.9.0"
|
||||
timsort = "0.1.2"
|
||||
tk-sys = { git = "https://github.com/arihant2math/tkinter.git", tag = "v0.2.0" }
|
||||
icu_casemap = "2"
|
||||
icu_locale = "2"
|
||||
icu_properties = "2"
|
||||
icu_normalizer = "2"
|
||||
uuid = "1.23.1"
|
||||
ucd = "0.1.1"
|
||||
unic-ucd-age = "0.9.0"
|
||||
unic-ucd-bidi = "0.9.0"
|
||||
unic-ucd-category = "0.9.0"
|
||||
unic-ucd-ident = "0.9.0"
|
||||
unicode_names2 = "2.0.0"
|
||||
unicode-bidi-mirroring = "0.4"
|
||||
widestring = "1.2.0"
|
||||
windows-sys = "0.61.2"
|
||||
wasm-bindgen = "0.2.106"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
webpki-roots = "1.0"
|
||||
which = "8"
|
||||
x509-cert = "0.2.5"
|
||||
x509-parser = "0.18"
|
||||
xml = "1.2"
|
||||
writeable = "0.6"
|
||||
|
||||
# Lints
|
||||
|
||||
@@ -234,13 +331,61 @@ wasm-bindgen = "0.2.106"
|
||||
unsafe_code = "allow"
|
||||
unsafe_op_in_unsafe_fn = "deny"
|
||||
elided_lifetimes_in_paths = "warn"
|
||||
unreachable_pub = "warn"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
correctness = { level = "warn", priority = -2 }
|
||||
suspicious = { level = "warn", priority = -2 }
|
||||
perf = { level = "warn", priority = -2 }
|
||||
style = { level = "warn", priority = -2 }
|
||||
complexity = { level = "warn", priority = -2 }
|
||||
# pedantic = { level = "warn", priority = -2 } # TODO: Enable this
|
||||
|
||||
missing_errors_doc = "allow" # Too many errors. No auto-fix available
|
||||
missing_panics_doc = "allow" # Too many errors. No auto-fix available
|
||||
match_same_arms = "allow" # Not always more readable
|
||||
if_not_else = "allow" # Not always more readable
|
||||
single_match_else = "allow"
|
||||
similar_names = "allow"
|
||||
|
||||
# restriction lints
|
||||
alloc_instead_of_core = "warn"
|
||||
cfg_not_test = "warn"
|
||||
redundant_test_prefix = "warn"
|
||||
std_instead_of_alloc = "warn"
|
||||
std_instead_of_core = "warn"
|
||||
perf = "warn"
|
||||
style = "warn"
|
||||
complexity = "warn"
|
||||
suspicious = "warn"
|
||||
correctness = "warn"
|
||||
tests_outside_test_module = "warn"
|
||||
|
||||
# nursery lints to enforce gradually
|
||||
debug_assert_with_mut_call = "warn"
|
||||
derive_partial_eq_without_eq = "warn"
|
||||
imprecise_flops = "warn"
|
||||
or_fun_call = "warn"
|
||||
redundant_clone = "warn"
|
||||
search_is_some = "warn"
|
||||
single_option_map = "warn"
|
||||
trait_duplication_in_bounds = "warn"
|
||||
unused_peekable = "warn"
|
||||
unused_rounding = "warn"
|
||||
use_self = "warn"
|
||||
useless_let_if_seq = "warn"
|
||||
|
||||
# pedantic lints to enforce gradually
|
||||
cloned_instead_of_copied = "warn"
|
||||
collapsible_else_if = "warn"
|
||||
comparison_chain = "warn"
|
||||
explicit_into_iter_loop = "warn"
|
||||
explicit_iter_loop = "warn"
|
||||
filter_map_next = "warn"
|
||||
flat_map_option = "warn"
|
||||
format_collect = "warn"
|
||||
from_iter_instead_of_collect = "warn"
|
||||
inconsistent_struct_constructor = "warn"
|
||||
inefficient_to_string = "warn"
|
||||
manual_is_variant_and = "warn"
|
||||
map_unwrap_or = "warn"
|
||||
must_use_candidate = "warn"
|
||||
redundant_else = "warn"
|
||||
uninlined_format_args = "warn"
|
||||
unnecessary_wraps = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 RustPython Team
|
||||
Copyright (c) 2026 RustPython Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
7
Lib/_android_support.py
vendored
7
Lib/_android_support.py
vendored
@@ -168,6 +168,13 @@ class Logcat:
|
||||
# message.
|
||||
message = message.replace(b"\x00", b"\xc0\x80")
|
||||
|
||||
# On API level 30 and higher, Logcat will strip any number of leading
|
||||
# newlines. This is visible in all `logcat` modes, even --binary. Work
|
||||
# around this by adding a leading space, which shouldn't make any
|
||||
# difference to the log's usability.
|
||||
if message.startswith(b"\n"):
|
||||
message = b" " + message
|
||||
|
||||
with self._lock:
|
||||
now = time()
|
||||
self._bucket_level += (
|
||||
|
||||
2
Lib/_opcode_metadata.py
generated
vendored
2
Lib/_opcode_metadata.py
generated
vendored
@@ -1,4 +1,4 @@
|
||||
# This file is generated by scripts/generate_opcode_metadata.py
|
||||
# This file is generated by tools/opcode_metadata/generate_py_opcode_metadata.py
|
||||
# for RustPython bytecode format (CPython 3.14 compatible opcode numbers).
|
||||
# Do not edit!
|
||||
|
||||
|
||||
55
Lib/annotationlib.py
vendored
55
Lib/annotationlib.py
vendored
@@ -47,6 +47,7 @@ _SLOTS = (
|
||||
"__cell__",
|
||||
"__owner__",
|
||||
"__stringifier_dict__",
|
||||
"__resolved_str_cache__",
|
||||
)
|
||||
|
||||
|
||||
@@ -94,6 +95,7 @@ class ForwardRef:
|
||||
# value later.
|
||||
self.__code__ = None
|
||||
self.__ast_node__ = None
|
||||
self.__resolved_str_cache__ = None
|
||||
|
||||
def __init_subclass__(cls, /, *args, **kwds):
|
||||
raise TypeError("Cannot subclass ForwardRef")
|
||||
@@ -113,7 +115,7 @@ class ForwardRef:
|
||||
"""
|
||||
match format:
|
||||
case Format.STRING:
|
||||
return self.__forward_arg__
|
||||
return self.__resolved_str__
|
||||
case Format.VALUE:
|
||||
is_forwardref_format = False
|
||||
case Format.FORWARDREF:
|
||||
@@ -258,6 +260,24 @@ class ForwardRef:
|
||||
"Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
|
||||
)
|
||||
|
||||
@property
|
||||
def __resolved_str__(self):
|
||||
# __forward_arg__ with any names from __extra_names__ replaced
|
||||
# with the type_repr of the value they represent
|
||||
if self.__resolved_str_cache__ is None:
|
||||
resolved_str = self.__forward_arg__
|
||||
names = self.__extra_names__
|
||||
|
||||
if names:
|
||||
visitor = _ExtraNameFixer(names)
|
||||
ast_expr = ast.parse(resolved_str, mode="eval").body
|
||||
node = visitor.visit(ast_expr)
|
||||
resolved_str = ast.unparse(node)
|
||||
|
||||
self.__resolved_str_cache__ = resolved_str
|
||||
|
||||
return self.__resolved_str_cache__
|
||||
|
||||
@property
|
||||
def __forward_code__(self):
|
||||
if self.__code__ is not None:
|
||||
@@ -321,7 +341,7 @@ class ForwardRef:
|
||||
extra.append(", is_class=True")
|
||||
if self.__owner__ is not None:
|
||||
extra.append(f", owner={self.__owner__!r}")
|
||||
return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
|
||||
return f"ForwardRef({self.__resolved_str__!r}{''.join(extra)})"
|
||||
|
||||
|
||||
_Template = type(t"")
|
||||
@@ -357,6 +377,7 @@ class _Stringifier:
|
||||
self.__cell__ = cell
|
||||
self.__owner__ = owner
|
||||
self.__stringifier_dict__ = stringifier_dict
|
||||
self.__resolved_str_cache__ = None # Needed for ForwardRef
|
||||
|
||||
def __convert_to_ast(self, other):
|
||||
if isinstance(other, _Stringifier):
|
||||
@@ -919,7 +940,7 @@ def get_annotations(
|
||||
does not exist, the __annotate__ function is called. The
|
||||
FORWARDREF format uses __annotations__ if it exists and can be
|
||||
evaluated, and otherwise falls back to calling the __annotate__ function.
|
||||
The SOURCE format tries __annotate__ first, and falls back to
|
||||
The STRING format tries __annotate__ first, and falls back to
|
||||
using __annotations__, stringified using annotations_to_string().
|
||||
|
||||
This function handles several details for you:
|
||||
@@ -1037,13 +1058,26 @@ def get_annotations(
|
||||
obj_globals = obj_locals = unwrap = None
|
||||
|
||||
if unwrap is not None:
|
||||
# Use an id-based visited set to detect cycles in the __wrapped__
|
||||
# and functools.partial.func chain (e.g. f.__wrapped__ = f).
|
||||
# On cycle detection we stop and use whatever __globals__ we have
|
||||
# found so far, mirroring the approach of inspect.unwrap().
|
||||
_seen_ids = {id(unwrap)}
|
||||
while True:
|
||||
if hasattr(unwrap, "__wrapped__"):
|
||||
unwrap = unwrap.__wrapped__
|
||||
candidate = unwrap.__wrapped__
|
||||
if id(candidate) in _seen_ids:
|
||||
break
|
||||
_seen_ids.add(id(candidate))
|
||||
unwrap = candidate
|
||||
continue
|
||||
if functools := sys.modules.get("functools"):
|
||||
if isinstance(unwrap, functools.partial):
|
||||
unwrap = unwrap.func
|
||||
candidate = unwrap.func
|
||||
if id(candidate) in _seen_ids:
|
||||
break
|
||||
_seen_ids.add(id(candidate))
|
||||
unwrap = candidate
|
||||
continue
|
||||
break
|
||||
if hasattr(unwrap, "__globals__"):
|
||||
@@ -1150,3 +1184,14 @@ def _get_dunder_annotations(obj):
|
||||
if not isinstance(ann, dict):
|
||||
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
|
||||
return ann
|
||||
|
||||
|
||||
class _ExtraNameFixer(ast.NodeTransformer):
|
||||
"""Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
|
||||
def __init__(self, extra_names):
|
||||
self.extra_names = extra_names
|
||||
|
||||
def visit_Name(self, node: ast.Name):
|
||||
if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:
|
||||
node = ast.Name(id=type_repr(new_name))
|
||||
return node
|
||||
|
||||
12
Lib/argparse.py
vendored
12
Lib/argparse.py
vendored
@@ -149,6 +149,10 @@ def _copy_items(items):
|
||||
return copy.copy(items)
|
||||
|
||||
|
||||
def _identity(value):
|
||||
return value
|
||||
|
||||
|
||||
# ===============
|
||||
# Formatting Help
|
||||
# ===============
|
||||
@@ -200,7 +204,7 @@ class HelpFormatter(object):
|
||||
self._decolor = decolor
|
||||
else:
|
||||
self._theme = get_theme(force_no_color=True).argparse
|
||||
self._decolor = lambda text: text
|
||||
self._decolor = _identity
|
||||
|
||||
# ===============================
|
||||
# Section and indentation methods
|
||||
@@ -1903,9 +1907,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
self._subparsers = None
|
||||
|
||||
# register types
|
||||
def identity(string):
|
||||
return string
|
||||
self.register('type', None, identity)
|
||||
self.register('type', None, _identity)
|
||||
|
||||
# add help argument if necessary
|
||||
# (using explicit default to override global argument_default)
|
||||
@@ -2676,7 +2678,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
||||
|
||||
if value not in choices:
|
||||
args = {'value': str(value),
|
||||
'choices': ', '.join(map(str, action.choices))}
|
||||
'choices': ', '.join(repr(str(choice)) for choice in action.choices)}
|
||||
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
|
||||
|
||||
if self.suggest_on_error and isinstance(value, str):
|
||||
|
||||
4
Lib/collections/_defaultdict.py
vendored
4
Lib/collections/_defaultdict.py
vendored
@@ -17,6 +17,10 @@ class defaultdict(dict):
|
||||
val = self.default_factory()
|
||||
else:
|
||||
raise KeyError(key)
|
||||
# CPython parity: a recursive __missing__ via factory() may have
|
||||
# already populated key; preserve that value instead of overwriting.
|
||||
if key in self:
|
||||
return self[key]
|
||||
self[key] = val
|
||||
return val
|
||||
|
||||
|
||||
17
Lib/configparser.py
vendored
17
Lib/configparser.py
vendored
@@ -315,12 +315,15 @@ class ParsingError(Error):
|
||||
|
||||
def append(self, lineno, line):
|
||||
self.errors.append((lineno, line))
|
||||
self.message += '\n\t[line %2d]: %s' % (lineno, repr(line))
|
||||
self.message += f'\n\t[line {lineno:2d}]: {line!r}'
|
||||
|
||||
def combine(self, others):
|
||||
messages = [self.message]
|
||||
for other in others:
|
||||
for error in other.errors:
|
||||
self.append(*error)
|
||||
for lineno, line in other.errors:
|
||||
self.errors.append((lineno, line))
|
||||
messages.append(f'\n\t[line {lineno:2d}]: {line!r}')
|
||||
self.message = "".join(messages)
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
@@ -613,7 +616,9 @@ class RawConfigParser(MutableMapping):
|
||||
\] # ]
|
||||
"""
|
||||
_OPT_TMPL = r"""
|
||||
(?P<option>.*?) # very permissive!
|
||||
(?P<option> # very permissive!
|
||||
(?:(?!{delim})\S)* # non-delimiter non-whitespace
|
||||
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
|
||||
\s*(?P<vi>{delim})\s* # any number of space/tab,
|
||||
# followed by any of the
|
||||
# allowed delimiters,
|
||||
@@ -621,7 +626,9 @@ class RawConfigParser(MutableMapping):
|
||||
(?P<value>.*)$ # everything up to eol
|
||||
"""
|
||||
_OPT_NV_TMPL = r"""
|
||||
(?P<option>.*?) # very permissive!
|
||||
(?P<option> # very permissive!
|
||||
(?:(?!{delim})\S)* # non-delimiter non-whitespace
|
||||
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
|
||||
\s*(?: # any number of space/tab,
|
||||
(?P<vi>{delim})\s* # optionally followed by
|
||||
# any of the allowed
|
||||
|
||||
2
Lib/ctypes/__init__.py
vendored
2
Lib/ctypes/__init__.py
vendored
@@ -470,6 +470,8 @@ class CDLL(object):
|
||||
if name and name.endswith(")") and ".a(" in name:
|
||||
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
|
||||
self._name = name
|
||||
if handle is not None:
|
||||
return handle
|
||||
return _dlopen(name, mode)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
26
Lib/ctypes/util.py
vendored
26
Lib/ctypes/util.py
vendored
@@ -85,15 +85,10 @@ if os.name == "nt":
|
||||
wintypes.DWORD,
|
||||
)
|
||||
|
||||
_psapi = ctypes.WinDLL('psapi', use_last_error=True)
|
||||
_enum_process_modules = _psapi["EnumProcessModules"]
|
||||
_enum_process_modules.restype = wintypes.BOOL
|
||||
_enum_process_modules.argtypes = (
|
||||
wintypes.HANDLE,
|
||||
ctypes.POINTER(wintypes.HMODULE),
|
||||
wintypes.DWORD,
|
||||
wintypes.LPDWORD,
|
||||
)
|
||||
# gh-145307: We defer loading psapi.dll until _get_module_handles is called.
|
||||
# Loading additional DLLs at startup for functionality that may never be
|
||||
# used is wasteful.
|
||||
_enum_process_modules = None
|
||||
|
||||
def _get_module_filename(module: wintypes.HMODULE):
|
||||
name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS
|
||||
@@ -101,8 +96,19 @@ if os.name == "nt":
|
||||
return name.value
|
||||
return None
|
||||
|
||||
|
||||
def _get_module_handles():
|
||||
global _enum_process_modules
|
||||
if _enum_process_modules is None:
|
||||
_psapi = ctypes.WinDLL('psapi', use_last_error=True)
|
||||
_enum_process_modules = _psapi["EnumProcessModules"]
|
||||
_enum_process_modules.restype = wintypes.BOOL
|
||||
_enum_process_modules.argtypes = (
|
||||
wintypes.HANDLE,
|
||||
ctypes.POINTER(wintypes.HMODULE),
|
||||
wintypes.DWORD,
|
||||
wintypes.LPDWORD,
|
||||
)
|
||||
|
||||
process = _get_current_process()
|
||||
space_needed = wintypes.DWORD()
|
||||
n = 1024
|
||||
|
||||
26
Lib/dataclasses.py
vendored
26
Lib/dataclasses.py
vendored
@@ -725,10 +725,10 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
|
||||
annotation_fields=annotation_fields)
|
||||
|
||||
|
||||
def _frozen_get_del_attr(cls, fields, func_builder):
|
||||
locals = {'cls': cls,
|
||||
def _frozen_set_del_attr(cls, fields, func_builder):
|
||||
locals = {'__class__': cls,
|
||||
'FrozenInstanceError': FrozenInstanceError}
|
||||
condition = 'type(self) is cls'
|
||||
condition = 'type(self) is __class__'
|
||||
if fields:
|
||||
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
|
||||
|
||||
@@ -736,14 +736,14 @@ def _frozen_get_del_attr(cls, fields, func_builder):
|
||||
('self', 'name', 'value'),
|
||||
(f' if {condition}:',
|
||||
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
|
||||
f' super(cls, self).__setattr__(name, value)'),
|
||||
f' super(__class__, self).__setattr__(name, value)'),
|
||||
locals=locals,
|
||||
overwrite_error=True)
|
||||
func_builder.add_fn('__delattr__',
|
||||
('self', 'name'),
|
||||
(f' if {condition}:',
|
||||
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
||||
f' super(cls, self).__delattr__(name)'),
|
||||
f' super(__class__, self).__delattr__(name)'),
|
||||
locals=locals,
|
||||
overwrite_error=True)
|
||||
|
||||
@@ -1199,7 +1199,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
|
||||
overwrite_error='Consider using functools.total_ordering')
|
||||
|
||||
if frozen:
|
||||
_frozen_get_del_attr(cls, field_list, func_builder)
|
||||
_frozen_set_del_attr(cls, field_list, func_builder)
|
||||
|
||||
# Decide if/how we're going to create a hash function.
|
||||
hash_action = _hash_action[bool(unsafe_hash),
|
||||
@@ -1292,10 +1292,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
|
||||
# This function doesn't reference __class__, so nothing to do.
|
||||
return False
|
||||
# Fix the cell to point to the new class, if it's already pointing
|
||||
# at the old class. I'm not convinced that the "is oldcls" test
|
||||
# is needed, but other than performance can't hurt.
|
||||
# at the old class.
|
||||
closure = f.__closure__[idx]
|
||||
if closure.cell_contents is oldcls:
|
||||
|
||||
try:
|
||||
contents = closure.cell_contents
|
||||
except ValueError:
|
||||
# Cell is empty
|
||||
return False
|
||||
|
||||
# This check makes it so we avoid updating an incorrect cell if the
|
||||
# class body contains a function that was defined in a different class.
|
||||
if contents is oldcls:
|
||||
closure.cell_contents = newcls
|
||||
return True
|
||||
return False
|
||||
|
||||
2
Lib/email/__init__.py
vendored
2
Lib/email/__init__.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2007 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/_encoded_words.py
vendored
2
Lib/email/_encoded_words.py
vendored
@@ -219,7 +219,7 @@ def encode(string, charset='utf-8', encoding=None, lang=''):
|
||||
|
||||
"""
|
||||
if charset == 'unknown-8bit':
|
||||
bstring = string.encode('ascii', 'surrogateescape')
|
||||
bstring = string.encode('utf-8', 'surrogateescape')
|
||||
else:
|
||||
bstring = string.encode(charset)
|
||||
if encoding is None:
|
||||
|
||||
138
Lib/email/_header_value_parser.py
vendored
138
Lib/email/_header_value_parser.py
vendored
@@ -80,7 +80,8 @@ from email import utils
|
||||
# Useful constants and functions
|
||||
#
|
||||
|
||||
WSP = set(' \t')
|
||||
_WSP = ' \t'
|
||||
WSP = set(_WSP)
|
||||
CFWS_LEADER = WSP | set('(')
|
||||
SPECIALS = set(r'()<>@,:;.\"[]')
|
||||
ATOM_ENDS = SPECIALS | WSP
|
||||
@@ -101,6 +102,12 @@ def make_quoted_pairs(value):
|
||||
return str(value).replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
|
||||
def make_parenthesis_pairs(value):
|
||||
"""Escape parenthesis and backslash for use within a comment."""
|
||||
return str(value).replace('\\', '\\\\') \
|
||||
.replace('(', '\\(').replace(')', '\\)')
|
||||
|
||||
|
||||
def quote_string(value):
|
||||
escaped = make_quoted_pairs(value)
|
||||
return f'"{escaped}"'
|
||||
@@ -632,11 +639,11 @@ class LocalPart(TokenList):
|
||||
for tok in self[0] + [DOT]:
|
||||
if tok.token_type == 'cfws':
|
||||
continue
|
||||
if (last_is_tl and tok.token_type == 'dot' and
|
||||
if (last_is_tl and tok.token_type == 'dot' and last and
|
||||
last[-1].token_type == 'cfws'):
|
||||
res[-1] = TokenList(last[:-1])
|
||||
is_tl = isinstance(tok, TokenList)
|
||||
if (is_tl and last.token_type == 'dot' and
|
||||
if (is_tl and last.token_type == 'dot' and tok and
|
||||
tok[0].token_type == 'cfws'):
|
||||
res.append(TokenList(tok[1:]))
|
||||
else:
|
||||
@@ -874,6 +881,12 @@ class MessageID(MsgID):
|
||||
class InvalidMessageID(MessageID):
|
||||
token_type = 'invalid-message-id'
|
||||
|
||||
class MessageIDList(TokenList):
|
||||
token_type = 'message-id-list'
|
||||
|
||||
@property
|
||||
def message_ids(self):
|
||||
return [x for x in self if x.token_type=='msg-id']
|
||||
|
||||
class Header(TokenList):
|
||||
token_type = 'header'
|
||||
@@ -933,7 +946,7 @@ class WhiteSpaceTerminal(Terminal):
|
||||
return ' '
|
||||
|
||||
def startswith_fws(self):
|
||||
return True
|
||||
return self and self[0] in WSP
|
||||
|
||||
|
||||
class ValueTerminal(Terminal):
|
||||
@@ -1232,8 +1245,7 @@ def get_bare_quoted_string(value):
|
||||
bare_quoted_string = BareQuotedString()
|
||||
value = value[1:]
|
||||
if value and value[0] == '"':
|
||||
token, value = get_qcontent(value)
|
||||
bare_quoted_string.append(token)
|
||||
return bare_quoted_string, value[1:]
|
||||
while value and value[0] != '"':
|
||||
if value[0] in WSP:
|
||||
token, value = get_fws(value)
|
||||
@@ -2046,12 +2058,10 @@ def get_address_list(value):
|
||||
address_list.defects.append(errors.InvalidHeaderDefect(
|
||||
"invalid address in address-list"))
|
||||
if value and value[0] != ',':
|
||||
# Crap after address; treat it as an invalid mailbox.
|
||||
# The mailbox info will still be available.
|
||||
mailbox = address_list[-1][0]
|
||||
mailbox.token_type = 'invalid-mailbox'
|
||||
# Crap after address: add it to the address list
|
||||
# as an invalid mailbox
|
||||
token, value = get_invalid_mailbox(value, ',')
|
||||
mailbox.extend(token)
|
||||
address_list.append(Address([token]))
|
||||
address_list.defects.append(errors.InvalidHeaderDefect(
|
||||
"invalid address in address-list"))
|
||||
if value: # Must be a , at this point.
|
||||
@@ -2171,6 +2181,32 @@ def parse_message_id(value):
|
||||
|
||||
return message_id
|
||||
|
||||
def parse_message_ids(value):
|
||||
"""in-reply-to = "In-Reply-To:" 1*msg-id CRLF
|
||||
references = "References:" 1*msg-id CRLF
|
||||
"""
|
||||
message_id_list = MessageIDList()
|
||||
while value:
|
||||
if value[0] == ',':
|
||||
# message id list separated with commas - this is invalid,
|
||||
# but happens rather frequently in the wild
|
||||
message_id_list.defects.append(
|
||||
errors.InvalidHeaderDefect("comma in msg-id list"))
|
||||
message_id_list.append(
|
||||
WhiteSpaceTerminal(' ', 'invalid-comma-replacement'))
|
||||
value = value[1:]
|
||||
continue
|
||||
try:
|
||||
token, value = get_msg_id(value)
|
||||
message_id_list.append(token)
|
||||
except errors.HeaderParseError as ex:
|
||||
token = get_unstructured(value)
|
||||
message_id_list.append(InvalidMessageID(token))
|
||||
message_id_list.defects.append(
|
||||
errors.InvalidHeaderDefect("Invalid msg-id: {!r}".format(ex)))
|
||||
break
|
||||
return message_id_list
|
||||
|
||||
#
|
||||
# XXX: As I begin to add additional header parsers, I'm realizing we probably
|
||||
# have two level of parser routines: the get_XXX methods that get a token in
|
||||
@@ -2788,8 +2824,12 @@ def _steal_trailing_WSP_if_exists(lines):
|
||||
if lines and lines[-1] and lines[-1][-1] in WSP:
|
||||
wsp = lines[-1][-1]
|
||||
lines[-1] = lines[-1][:-1]
|
||||
# gh-142006: if the line is now empty, remove it entirely.
|
||||
if not lines[-1]:
|
||||
lines.pop()
|
||||
return wsp
|
||||
|
||||
|
||||
def _refold_parse_tree(parse_tree, *, policy):
|
||||
"""Return string of contents of parse_tree folded according to RFC rules.
|
||||
|
||||
@@ -2798,11 +2838,9 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
maxlen = policy.max_line_length or sys.maxsize
|
||||
encoding = 'utf-8' if policy.utf8 else 'us-ascii'
|
||||
lines = [''] # Folded lines to be output
|
||||
leading_whitespace = '' # When we have whitespace between two encoded
|
||||
# words, we may need to encode the whitespace
|
||||
# at the beginning of the second word.
|
||||
last_ew = None # Points to the last encoded character if there's an ew on
|
||||
# the line
|
||||
last_word_is_ew = False
|
||||
last_ew = None # if there is an encoded word in the last line of lines,
|
||||
# points to the encoded word's first character
|
||||
last_charset = None
|
||||
wrap_as_ew_blocked = 0
|
||||
want_encoding = False # This is set to True if we need to encode this part
|
||||
@@ -2837,6 +2875,7 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
if part.token_type == 'mime-parameters':
|
||||
# Mime parameter folding (using RFC2231) is extra special.
|
||||
_fold_mime_parameters(part, lines, maxlen, encoding)
|
||||
last_word_is_ew = False
|
||||
continue
|
||||
|
||||
if want_encoding and not wrap_as_ew_blocked:
|
||||
@@ -2853,6 +2892,7 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
# XXX what if encoded_part has no leading FWS?
|
||||
lines.append(newline)
|
||||
lines[-1] += encoded_part
|
||||
last_word_is_ew = False
|
||||
continue
|
||||
# Either this is not a major syntactic break, so we don't
|
||||
# want it on a line by itself even if it fits, or it
|
||||
@@ -2871,11 +2911,16 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
(last_charset == 'unknown-8bit' or
|
||||
last_charset == 'utf-8' and charset != 'us-ascii')):
|
||||
last_ew = None
|
||||
last_ew = _fold_as_ew(tstr, lines, maxlen, last_ew,
|
||||
part.ew_combine_allowed, charset, leading_whitespace)
|
||||
# This whitespace has been added to the lines in _fold_as_ew()
|
||||
# so clear it now.
|
||||
leading_whitespace = ''
|
||||
last_ew = _fold_as_ew(
|
||||
tstr,
|
||||
lines,
|
||||
maxlen,
|
||||
last_ew,
|
||||
part.ew_combine_allowed,
|
||||
charset,
|
||||
last_word_is_ew,
|
||||
)
|
||||
last_word_is_ew = True
|
||||
last_charset = charset
|
||||
want_encoding = False
|
||||
continue
|
||||
@@ -2888,28 +2933,19 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
|
||||
if len(tstr) <= maxlen - len(lines[-1]):
|
||||
lines[-1] += tstr
|
||||
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
|
||||
continue
|
||||
|
||||
# This part is too long to fit. The RFC wants us to break at
|
||||
# "major syntactic breaks", so unless we don't consider this
|
||||
# to be one, check if it will fit on the next line by itself.
|
||||
leading_whitespace = ''
|
||||
if (part.syntactic_break and
|
||||
len(tstr) + 1 <= maxlen):
|
||||
newline = _steal_trailing_WSP_if_exists(lines)
|
||||
if newline or part.startswith_fws():
|
||||
# We're going to fold the data onto a new line here. Due to
|
||||
# the way encoded strings handle continuation lines, we need to
|
||||
# be prepared to encode any whitespace if the next line turns
|
||||
# out to start with an encoded word.
|
||||
lines.append(newline + tstr)
|
||||
|
||||
whitespace_accumulator = []
|
||||
for char in lines[-1]:
|
||||
if char not in WSP:
|
||||
break
|
||||
whitespace_accumulator.append(char)
|
||||
leading_whitespace = ''.join(whitespace_accumulator)
|
||||
last_word_is_ew = (last_word_is_ew
|
||||
and not bool(lines[-1].strip(_WSP)))
|
||||
last_ew = None
|
||||
continue
|
||||
if not hasattr(part, 'encode'):
|
||||
@@ -2924,6 +2960,13 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
[ValueTerminal(make_quoted_pairs(p), 'ptext')
|
||||
for p in newparts] +
|
||||
[ValueTerminal('"', 'ptext')])
|
||||
if part.token_type == 'comment':
|
||||
newparts = (
|
||||
[ValueTerminal('(', 'ptext')] +
|
||||
[ValueTerminal(make_parenthesis_pairs(p), 'ptext')
|
||||
if p.token_type == 'ptext' else p
|
||||
for p in newparts] +
|
||||
[ValueTerminal(')', 'ptext')])
|
||||
if not part.as_ew_allowed:
|
||||
wrap_as_ew_blocked += 1
|
||||
newparts.append(end_ew_not_allowed)
|
||||
@@ -2942,10 +2985,11 @@ def _refold_parse_tree(parse_tree, *, policy):
|
||||
else:
|
||||
# We can't fold it onto the next line either...
|
||||
lines[-1] += tstr
|
||||
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
|
||||
|
||||
return policy.linesep.join(lines) + policy.linesep
|
||||
|
||||
def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, leading_whitespace):
|
||||
def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, last_word_is_ew):
|
||||
"""Fold string to_encode into lines as encoded word, combining if allowed.
|
||||
Return the new value for last_ew, or None if ew_combine_allowed is False.
|
||||
|
||||
@@ -2960,6 +3004,16 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
|
||||
to_encode = str(
|
||||
get_unstructured(lines[-1][last_ew:] + to_encode))
|
||||
lines[-1] = lines[-1][:last_ew]
|
||||
elif last_word_is_ew:
|
||||
# If we are following up an encoded word with another encoded word,
|
||||
# any white space between the two will be ignored when decoded.
|
||||
# Therefore, we encode all to-be-displayed whitespace in the second
|
||||
# encoded word.
|
||||
len_without_wsp = len(lines[-1].rstrip(_WSP))
|
||||
leading_whitespace = lines[-1][len_without_wsp:]
|
||||
lines[-1] = (lines[-1][:len_without_wsp]
|
||||
+ (' ' if leading_whitespace else ''))
|
||||
to_encode = leading_whitespace + to_encode
|
||||
elif to_encode[0] in WSP:
|
||||
# We're joining this to non-encoded text, so don't encode
|
||||
# the leading blank.
|
||||
@@ -2988,20 +3042,13 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
|
||||
|
||||
while to_encode:
|
||||
remaining_space = maxlen - len(lines[-1])
|
||||
text_space = remaining_space - chrome_len - len(leading_whitespace)
|
||||
text_space = remaining_space - chrome_len
|
||||
if text_space <= 0:
|
||||
lines.append(' ')
|
||||
newline = _steal_trailing_WSP_if_exists(lines)
|
||||
lines.append(newline or ' ')
|
||||
new_last_ew = len(lines[-1])
|
||||
continue
|
||||
|
||||
# If we are at the start of a continuation line, prepend whitespace
|
||||
# (we only want to do this when the line starts with an encoded word
|
||||
# but if we're folding in this helper function, then we know that we
|
||||
# are going to be writing out an encoded word.)
|
||||
if len(lines) > 1 and len(lines[-1]) == 1 and leading_whitespace:
|
||||
encoded_word = _ew.encode(leading_whitespace, charset=encode_as)
|
||||
lines[-1] += encoded_word
|
||||
leading_whitespace = ''
|
||||
|
||||
to_encode_word = to_encode[:text_space]
|
||||
encoded_word = _ew.encode(to_encode_word, charset=encode_as)
|
||||
excess = len(encoded_word) - remaining_space
|
||||
@@ -3013,7 +3060,6 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
|
||||
excess = len(encoded_word) - remaining_space
|
||||
lines[-1] += encoded_word
|
||||
to_encode = to_encode[len(to_encode_word):]
|
||||
leading_whitespace = ''
|
||||
|
||||
if to_encode:
|
||||
lines.append(' ')
|
||||
|
||||
14
Lib/email/_parseaddr.py
vendored
14
Lib/email/_parseaddr.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2002-2007 Python Software Foundation
|
||||
# Copyright (C) 2002 Python Software Foundation
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
"""Email address parsing code.
|
||||
@@ -225,7 +225,7 @@ class AddrlistClass:
|
||||
def __init__(self, field):
|
||||
"""Initialize a new instance.
|
||||
|
||||
`field' is an unparsed address header field, containing
|
||||
'field' is an unparsed address header field, containing
|
||||
one or more addresses.
|
||||
"""
|
||||
self.specials = '()<>@,:;.\"[]'
|
||||
@@ -426,14 +426,14 @@ class AddrlistClass:
|
||||
def getdelimited(self, beginchar, endchars, allowcomments=True):
|
||||
"""Parse a header fragment delimited by special characters.
|
||||
|
||||
`beginchar' is the start character for the fragment.
|
||||
If self is not looking at an instance of `beginchar' then
|
||||
'beginchar' is the start character for the fragment.
|
||||
If self is not looking at an instance of 'beginchar' then
|
||||
getdelimited returns the empty string.
|
||||
|
||||
`endchars' is a sequence of allowable end-delimiting characters.
|
||||
'endchars' is a sequence of allowable end-delimiting characters.
|
||||
Parsing stops when one of these is encountered.
|
||||
|
||||
If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
|
||||
If 'allowcomments' is non-zero, embedded RFC 2822 comments are allowed
|
||||
within the parsed fragment.
|
||||
"""
|
||||
if self.field[self.pos] != beginchar:
|
||||
@@ -477,7 +477,7 @@ class AddrlistClass:
|
||||
|
||||
Optional atomends specifies a different set of end token delimiters
|
||||
(the default is to use self.atomends). This is used e.g. in
|
||||
getphraselist() since phrase endings must not include the `.' (which
|
||||
getphraselist() since phrase endings must not include the '.' (which
|
||||
is legal in phrases)."""
|
||||
atomlist = ['']
|
||||
if atomends is None:
|
||||
|
||||
12
Lib/email/_policybase.py
vendored
12
Lib/email/_policybase.py
vendored
@@ -4,6 +4,7 @@ Allows fine grained feature control of how the package parses and emits data.
|
||||
"""
|
||||
|
||||
import abc
|
||||
import re
|
||||
from email import header
|
||||
from email import charset as _charset
|
||||
from email.utils import _has_surrogates
|
||||
@@ -14,6 +15,14 @@ __all__ = [
|
||||
'compat32',
|
||||
]
|
||||
|
||||
# validation regex from RFC 5322, equivalent to pattern re.compile("[!-9;-~]+$")
|
||||
valid_header_name_re = re.compile("[\041-\071\073-\176]+$")
|
||||
|
||||
def validate_header_name(name):
|
||||
# Validate header name according to RFC 5322
|
||||
if not valid_header_name_re.match(name):
|
||||
raise ValueError(
|
||||
f"Header field name contains invalid characters: {name!r}")
|
||||
|
||||
class _PolicyBase:
|
||||
|
||||
@@ -150,7 +159,7 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta):
|
||||
wrapping is done. Default is 78.
|
||||
|
||||
mangle_from_ -- a flag that, when True escapes From_ lines in the
|
||||
body of the message by putting a `>' in front of
|
||||
body of the message by putting a '>' in front of
|
||||
them. This is used when the message is being
|
||||
serialized by a generator. Default: False.
|
||||
|
||||
@@ -314,6 +323,7 @@ class Compat32(Policy):
|
||||
"""+
|
||||
The name and value are returned unmodified.
|
||||
"""
|
||||
validate_header_name(name)
|
||||
return (name, value)
|
||||
|
||||
def header_fetch_parse(self, name, value):
|
||||
|
||||
4
Lib/email/base64mime.py
vendored
4
Lib/email/base64mime.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2002-2007 Python Software Foundation
|
||||
# Copyright (C) 2002 Python Software Foundation
|
||||
# Author: Ben Gertzfield
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -15,7 +15,7 @@ This module provides an interface to encode and decode both headers and bodies
|
||||
with Base64 encoding.
|
||||
|
||||
RFC 2045 defines a method for including character set information in an
|
||||
`encoded-word' in a header. This method is commonly used for 8-bit real names
|
||||
'encoded-word' in a header. This method is commonly used for 8-bit real names
|
||||
in To:, From:, Cc:, etc. fields, as well as Subject: lines.
|
||||
|
||||
This module does not do the line wrapping or end-of-line character conversion
|
||||
|
||||
6
Lib/email/charset.py
vendored
6
Lib/email/charset.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2007 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Ben Gertzfield, Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -175,7 +175,7 @@ class Charset:
|
||||
module expose the following information about a character set:
|
||||
|
||||
input_charset: The initial character set specified. Common aliases
|
||||
are converted to their `official' email names (e.g. latin_1
|
||||
are converted to their 'official' email names (e.g. latin_1
|
||||
is converted to iso-8859-1). Defaults to 7-bit us-ascii.
|
||||
|
||||
header_encoding: If the character set must be encoded before it can be
|
||||
@@ -245,7 +245,7 @@ class Charset:
|
||||
def get_body_encoding(self):
|
||||
"""Return the content-transfer-encoding used for body encoding.
|
||||
|
||||
This is either the string `quoted-printable' or `base64' depending on
|
||||
This is either the string 'quoted-printable' or 'base64' depending on
|
||||
the encoding used, or it is a function in which case you should call
|
||||
the function with a single argument, the Message object being
|
||||
encoded. The function should then set the Content-Transfer-Encoding
|
||||
|
||||
2
Lib/email/encoders.py
vendored
2
Lib/email/encoders.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/errors.py
vendored
2
Lib/email/errors.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
11
Lib/email/feedparser.py
vendored
11
Lib/email/feedparser.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2004-2006 Python Software Foundation
|
||||
# Copyright (C) 2004 Python Software Foundation
|
||||
# Authors: Baxter, Wouters and Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -30,7 +30,7 @@ from io import StringIO
|
||||
|
||||
NLCRE = re.compile(r'\r\n|\r|\n')
|
||||
NLCRE_bol = re.compile(r'(\r\n|\r|\n)')
|
||||
NLCRE_eol = re.compile(r'(\r\n|\r|\n)\Z')
|
||||
NLCRE_eol = re.compile(r'(\r\n|\r|\n)\z')
|
||||
NLCRE_crack = re.compile(r'(\r\n|\r|\n)')
|
||||
# RFC 5322 section 3.6.8 Optional fields. ftext is %d33-57 / %d59-126, Any character
|
||||
# except controls, SP, and ":".
|
||||
@@ -504,10 +504,9 @@ class FeedParser:
|
||||
self._input.unreadline(line)
|
||||
return
|
||||
else:
|
||||
# Weirdly placed unix-from line. Note this as a defect
|
||||
# and ignore it.
|
||||
# Weirdly placed unix-from line.
|
||||
defect = errors.MisplacedEnvelopeHeaderDefect(line)
|
||||
self._cur.defects.append(defect)
|
||||
self.policy.handle_defect(self._cur, defect)
|
||||
continue
|
||||
# Split the line on the colon separating field name from value.
|
||||
# There will always be a colon, because if there wasn't the part of
|
||||
@@ -519,7 +518,7 @@ class FeedParser:
|
||||
# message. Track the error but keep going.
|
||||
if i == 0:
|
||||
defect = errors.InvalidHeaderDefect("Missing header name.")
|
||||
self._cur.defects.append(defect)
|
||||
self.policy.handle_defect(self._cur, defect)
|
||||
continue
|
||||
|
||||
assert i>0, "_parse_headers fed line with no : and no leading WS"
|
||||
|
||||
24
Lib/email/generator.py
vendored
24
Lib/email/generator.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2010 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -22,6 +22,7 @@ NL = '\n' # XXX: no longer used by the code below.
|
||||
NLCRE = re.compile(r'\r\n|\r|\n')
|
||||
fcre = re.compile(r'^From ', re.MULTILINE)
|
||||
NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
|
||||
NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
|
||||
|
||||
|
||||
class Generator:
|
||||
@@ -43,7 +44,7 @@ class Generator:
|
||||
|
||||
Optional mangle_from_ is a flag that, when True (the default if policy
|
||||
is not set), escapes From_ lines in the body of the message by putting
|
||||
a `>' in front of them.
|
||||
a '>' in front of them.
|
||||
|
||||
Optional maxheaderlen specifies the longest length for a non-continued
|
||||
header. When a header line is longer (in characters, with tabs
|
||||
@@ -76,7 +77,7 @@ class Generator:
|
||||
|
||||
unixfrom is a flag that forces the printing of a Unix From_ delimiter
|
||||
before the first object in the message tree. If the original message
|
||||
has no From_ delimiter, a `standard' one is crafted. By default, this
|
||||
has no From_ delimiter, a 'standard' one is crafted. By default, this
|
||||
is False to inhibit the printing of any From_ delimiter.
|
||||
|
||||
Note that for subobjects, no From_ line is printed.
|
||||
@@ -227,7 +228,7 @@ class Generator:
|
||||
folded = self.policy.fold(h, v)
|
||||
if self.policy.verify_generated_headers:
|
||||
linesep = self.policy.linesep
|
||||
if not folded.endswith(self.policy.linesep):
|
||||
if not folded.endswith(linesep):
|
||||
raise HeaderWriteError(
|
||||
f'folded header does not end with {linesep!r}: {folded!r}')
|
||||
if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)):
|
||||
@@ -391,7 +392,7 @@ class Generator:
|
||||
b = boundary
|
||||
counter = 0
|
||||
while True:
|
||||
cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
|
||||
cre = cls._compile_re('^--' + re.escape(b) + '(--)?\r?$', re.MULTILINE)
|
||||
if not cre.search(text):
|
||||
break
|
||||
b = boundary + '.' + str(counter)
|
||||
@@ -429,7 +430,16 @@ class BytesGenerator(Generator):
|
||||
# This is almost the same as the string version, except for handling
|
||||
# strings with 8bit bytes.
|
||||
for h, v in msg.raw_items():
|
||||
self._fp.write(self.policy.fold_binary(h, v))
|
||||
folded = self.policy.fold_binary(h, v)
|
||||
if self.policy.verify_generated_headers:
|
||||
linesep = self.policy.linesep.encode()
|
||||
if not folded.endswith(linesep):
|
||||
raise HeaderWriteError(
|
||||
f'folded header does not end with {linesep!r}: {folded!r}')
|
||||
if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)):
|
||||
raise HeaderWriteError(
|
||||
f'folded header contains newline: {folded!r}')
|
||||
self._fp.write(folded)
|
||||
# A blank line always separates headers from body
|
||||
self.write(self._NL)
|
||||
|
||||
@@ -467,7 +477,7 @@ class DecodedGenerator(Generator):
|
||||
argument is allowed.
|
||||
|
||||
Walks through all subparts of a message. If the subpart is of main
|
||||
type `text', then it prints the decoded payload of the subpart.
|
||||
type 'text', then it prints the decoded payload of the subpart.
|
||||
|
||||
Otherwise, fmt is a format string that is used instead of the message
|
||||
payload. fmt is expanded with the following keywords (in
|
||||
|
||||
8
Lib/email/header.py
vendored
8
Lib/email/header.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2002-2007 Python Software Foundation
|
||||
# Copyright (C) 2002 Python Software Foundation
|
||||
# Author: Ben Gertzfield, Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -201,7 +201,7 @@ class Header:
|
||||
|
||||
The maximum line length can be specified explicitly via maxlinelen. For
|
||||
splitting the first line to a shorter value (to account for the field
|
||||
header which isn't included in s, e.g. `Subject') pass in the name of
|
||||
header which isn't included in s, e.g. 'Subject') pass in the name of
|
||||
the field in header_name. The default maxlinelen is 78 as recommended
|
||||
by RFC 2822.
|
||||
|
||||
@@ -285,7 +285,7 @@ class Header:
|
||||
output codec of the charset. If the string cannot be encoded to the
|
||||
output codec, a UnicodeError will be raised.
|
||||
|
||||
Optional `errors' is passed as the errors argument to the decode
|
||||
Optional 'errors' is passed as the errors argument to the decode
|
||||
call if s is a byte string.
|
||||
"""
|
||||
if charset is None:
|
||||
@@ -335,7 +335,7 @@ class Header:
|
||||
|
||||
Optional splitchars is a string containing characters which should be
|
||||
given extra weight by the splitting algorithm during normal header
|
||||
wrapping. This is in very rough support of RFC 2822's `higher level
|
||||
wrapping. This is in very rough support of RFC 2822's 'higher level
|
||||
syntactic breaks': split points preceded by a splitchar are preferred
|
||||
during line splitting, with the characters preferred in the order in
|
||||
which they appear in the string. Space and tab may be included in the
|
||||
|
||||
14
Lib/email/headerregistry.py
vendored
14
Lib/email/headerregistry.py
vendored
@@ -534,6 +534,18 @@ class MessageIDHeader:
|
||||
kwds['defects'].extend(parse_tree.all_defects)
|
||||
|
||||
|
||||
class ReferencesHeader:
|
||||
|
||||
max_count = 1
|
||||
value_parser = staticmethod(parser.parse_message_ids)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value, kwds):
|
||||
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
|
||||
kwds['decoded'] = str(parse_tree)
|
||||
kwds['defects'].extend(parse_tree.all_defects)
|
||||
|
||||
|
||||
# The header factory #
|
||||
|
||||
_default_header_map = {
|
||||
@@ -557,6 +569,8 @@ _default_header_map = {
|
||||
'content-disposition': ContentDispositionHeader,
|
||||
'content-transfer-encoding': ContentTransferEncodingHeader,
|
||||
'message-id': MessageIDHeader,
|
||||
'in-reply-to': ReferencesHeader,
|
||||
'references': ReferencesHeader,
|
||||
}
|
||||
|
||||
class HeaderRegistry:
|
||||
|
||||
6
Lib/email/iterators.py
vendored
6
Lib/email/iterators.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -43,8 +43,8 @@ def body_line_iterator(msg, decode=False):
|
||||
def typed_subpart_iterator(msg, maintype='text', subtype=None):
|
||||
"""Iterate over the subparts with a given MIME type.
|
||||
|
||||
Use `maintype' as the main MIME type to match against; this defaults to
|
||||
"text". Optional `subtype' is the MIME subtype to match against; if
|
||||
Use 'maintype' as the main MIME type to match against; this defaults to
|
||||
"text". Optional 'subtype' is the MIME subtype to match against; if
|
||||
omitted, only the main type is matched.
|
||||
"""
|
||||
for subpart in msg.walk():
|
||||
|
||||
28
Lib/email/message.py
vendored
28
Lib/email/message.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2007 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -21,7 +21,7 @@ Charset = _charset.Charset
|
||||
|
||||
SEMISPACE = '; '
|
||||
|
||||
# Regular expression that matches `special' characters in parameters, the
|
||||
# Regular expression that matches 'special' characters in parameters, the
|
||||
# existence of which force quoting of the parameter value.
|
||||
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
|
||||
|
||||
@@ -147,7 +147,7 @@ class Message:
|
||||
multipart or a message/rfc822), then the payload is a list of Message
|
||||
objects, otherwise it is a string.
|
||||
|
||||
Message objects implement part of the `mapping' interface, which assumes
|
||||
Message objects implement part of the 'mapping' interface, which assumes
|
||||
there is exactly one occurrence of the header per message. Some headers
|
||||
do in fact appear multiple times (e.g. Received) and for those headers,
|
||||
you must use the explicit API to set or get all the headers. Not all of
|
||||
@@ -609,7 +609,7 @@ class Message:
|
||||
"""Return the message's content type.
|
||||
|
||||
The returned string is coerced to lower case of the form
|
||||
`maintype/subtype'. If there was no Content-Type header in the
|
||||
'maintype/subtype'. If there was no Content-Type header in the
|
||||
message, the default type as given by get_default_type() will be
|
||||
returned. Since according to RFC 2045, messages always have a default
|
||||
type this will always return a value.
|
||||
@@ -632,7 +632,7 @@ class Message:
|
||||
def get_content_maintype(self):
|
||||
"""Return the message's main content type.
|
||||
|
||||
This is the `maintype' part of the string returned by
|
||||
This is the 'maintype' part of the string returned by
|
||||
get_content_type().
|
||||
"""
|
||||
ctype = self.get_content_type()
|
||||
@@ -641,14 +641,14 @@ class Message:
|
||||
def get_content_subtype(self):
|
||||
"""Returns the message's sub-content type.
|
||||
|
||||
This is the `subtype' part of the string returned by
|
||||
This is the 'subtype' part of the string returned by
|
||||
get_content_type().
|
||||
"""
|
||||
ctype = self.get_content_type()
|
||||
return ctype.split('/')[1]
|
||||
|
||||
def get_default_type(self):
|
||||
"""Return the `default' content type.
|
||||
"""Return the 'default' content type.
|
||||
|
||||
Most messages have a default content type of text/plain, except for
|
||||
messages that are subparts of multipart/digest containers. Such
|
||||
@@ -657,7 +657,7 @@ class Message:
|
||||
return self._default_type
|
||||
|
||||
def set_default_type(self, ctype):
|
||||
"""Set the `default' content type.
|
||||
"""Set the 'default' content type.
|
||||
|
||||
ctype should be either "text/plain" or "message/rfc822", although this
|
||||
is not enforced. The default content type is not stored in the
|
||||
@@ -690,8 +690,8 @@ class Message:
|
||||
"""Return the message's Content-Type parameters, as a list.
|
||||
|
||||
The elements of the returned list are 2-tuples of key/value pairs, as
|
||||
split on the `=' sign. The left hand side of the `=' is the key,
|
||||
while the right hand side is the value. If there is no `=' sign in
|
||||
split on the '=' sign. The left hand side of the '=' is the key,
|
||||
while the right hand side is the value. If there is no '=' sign in
|
||||
the parameter the value is the empty string. The value is as
|
||||
described in the get_param() method.
|
||||
|
||||
@@ -851,9 +851,9 @@ class Message:
|
||||
"""Return the filename associated with the payload if present.
|
||||
|
||||
The filename is extracted from the Content-Disposition header's
|
||||
`filename' parameter, and it is unquoted. If that header is missing
|
||||
the `filename' parameter, this method falls back to looking for the
|
||||
`name' parameter.
|
||||
'filename' parameter, and it is unquoted. If that header is missing
|
||||
the 'filename' parameter, this method falls back to looking for the
|
||||
'name' parameter.
|
||||
"""
|
||||
missing = object()
|
||||
filename = self.get_param('filename', missing, 'content-disposition')
|
||||
@@ -866,7 +866,7 @@ class Message:
|
||||
def get_boundary(self, failobj=None):
|
||||
"""Return the boundary associated with the payload if present.
|
||||
|
||||
The boundary is extracted from the Content-Type header's `boundary'
|
||||
The boundary is extracted from the Content-Type header's 'boundary'
|
||||
parameter, and it is unquoted.
|
||||
"""
|
||||
missing = object()
|
||||
|
||||
2
Lib/email/mime/application.py
vendored
2
Lib/email/mime/application.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Keith Dart
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/mime/audio.py
vendored
2
Lib/email/mime/audio.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2007 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Anthony Baxter
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/mime/base.py
vendored
2
Lib/email/mime/base.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/mime/image.py
vendored
2
Lib/email/mime/image.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/mime/message.py
vendored
2
Lib/email/mime/message.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
4
Lib/email/mime/multipart.py
vendored
4
Lib/email/mime/multipart.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2002-2006 Python Software Foundation
|
||||
# Copyright (C) 2002 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -21,7 +21,7 @@ class MIMEMultipart(MIMEBase):
|
||||
Content-Type and MIME-Version headers.
|
||||
|
||||
_subtype is the subtype of the multipart content type, defaulting to
|
||||
`mixed'.
|
||||
'mixed'.
|
||||
|
||||
boundary is the multipart boundary string. By default it is
|
||||
calculated as needed.
|
||||
|
||||
2
Lib/email/mime/nonmultipart.py
vendored
2
Lib/email/mime/nonmultipart.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2002-2006 Python Software Foundation
|
||||
# Copyright (C) 2002 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/mime/text.py
vendored
2
Lib/email/mime/text.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
2
Lib/email/parser.py
vendored
2
Lib/email/parser.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2007 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
|
||||
9
Lib/email/policy.py
vendored
9
Lib/email/policy.py
vendored
@@ -4,7 +4,13 @@ code that adds all the email6 features.
|
||||
|
||||
import re
|
||||
import sys
|
||||
from email._policybase import Policy, Compat32, compat32, _extend_docstrings
|
||||
from email._policybase import (
|
||||
Compat32,
|
||||
Policy,
|
||||
_extend_docstrings,
|
||||
compat32,
|
||||
validate_header_name
|
||||
)
|
||||
from email.utils import _has_surrogates
|
||||
from email.headerregistry import HeaderRegistry as HeaderRegistry
|
||||
from email.contentmanager import raw_data_manager
|
||||
@@ -138,6 +144,7 @@ class EmailPolicy(Policy):
|
||||
CR or LF characters.
|
||||
|
||||
"""
|
||||
validate_header_name(name)
|
||||
if hasattr(value, 'name') and value.name.lower() == name.lower():
|
||||
return (name, value)
|
||||
if isinstance(value, str) and len(value.splitlines())>1:
|
||||
|
||||
12
Lib/email/quoprimime.py
vendored
12
Lib/email/quoprimime.py
vendored
@@ -1,11 +1,11 @@
|
||||
# Copyright (C) 2001-2006 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Ben Gertzfield
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
"""Quoted-printable content transfer encoding per RFCs 2045-2047.
|
||||
|
||||
This module handles the content transfer encoding method defined in RFC 2045
|
||||
to encode US ASCII-like 8-bit data called `quoted-printable'. It is used to
|
||||
to encode US ASCII-like 8-bit data called 'quoted-printable'. It is used to
|
||||
safely encode text that is in a character set similar to the 7-bit US ASCII
|
||||
character set, but that includes some 8-bit characters that are normally not
|
||||
allowed in email bodies or headers.
|
||||
@@ -17,7 +17,7 @@ This module provides an interface to encode and decode both headers and bodies
|
||||
with quoted-printable encoding.
|
||||
|
||||
RFC 2045 defines a method for including character set information in an
|
||||
`encoded-word' in a header. This method is commonly used for 8-bit real names
|
||||
'encoded-word' in a header. This method is commonly used for 8-bit real names
|
||||
in To:/From:/Cc: etc. fields, as well as Subject: lines.
|
||||
|
||||
This module does not do the line wrapping or end-of-line character
|
||||
@@ -127,7 +127,7 @@ def quote(c):
|
||||
def header_encode(header_bytes, charset='iso-8859-1'):
|
||||
"""Encode a single header line with quoted-printable (like) encoding.
|
||||
|
||||
Defined in RFC 2045, this `Q' encoding is similar to quoted-printable, but
|
||||
Defined in RFC 2045, this 'Q' encoding is similar to quoted-printable, but
|
||||
used specifically for email header fields to allow charsets with mostly 7
|
||||
bit characters (and some 8 bit) to remain more or less readable in non-RFC
|
||||
2045 aware mail clients.
|
||||
@@ -272,7 +272,7 @@ def decode(encoded, eol=NL):
|
||||
decoded += eol
|
||||
# Special case if original string did not end with eol
|
||||
if encoded[-1] not in '\r\n' and decoded.endswith(eol):
|
||||
decoded = decoded[:-1]
|
||||
decoded = decoded[:-len(eol)]
|
||||
return decoded
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ def _unquote_match(match):
|
||||
|
||||
# Header decoding is done a bit differently
|
||||
def header_decode(s):
|
||||
"""Decode a string encoded with RFC 2045 MIME header `Q' encoding.
|
||||
"""Decode a string encoded with RFC 2045 MIME header 'Q' encoding.
|
||||
|
||||
This function does not parse a full MIME header value encoded with
|
||||
quoted-printable (like =?iso-8859-1?q?Hello_World?=) -- please use
|
||||
|
||||
12
Lib/email/utils.py
vendored
12
Lib/email/utils.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2001-2010 Python Software Foundation
|
||||
# Copyright (C) 2001 Python Software Foundation
|
||||
# Author: Barry Warsaw
|
||||
# Contact: email-sig@python.org
|
||||
|
||||
@@ -472,23 +472,15 @@ def collapse_rfc2231_value(value, errors='replace',
|
||||
# better than not having it.
|
||||
#
|
||||
|
||||
def localtime(dt=None, isdst=None):
|
||||
def localtime(dt=None):
|
||||
"""Return local time as an aware datetime object.
|
||||
|
||||
If called without arguments, return current time. Otherwise *dt*
|
||||
argument should be a datetime instance, and it is converted to the
|
||||
local time zone according to the system time zone database. If *dt* is
|
||||
naive (that is, dt.tzinfo is None), it is assumed to be in local time.
|
||||
The isdst parameter is ignored.
|
||||
|
||||
"""
|
||||
if isdst is not None:
|
||||
import warnings
|
||||
warnings._deprecated(
|
||||
"The 'isdst' parameter to 'localtime'",
|
||||
message='{name} is deprecated and slated for removal in Python {remove}',
|
||||
remove=(3, 14),
|
||||
)
|
||||
if dt is None:
|
||||
dt = datetime.datetime.now()
|
||||
return dt.astimezone()
|
||||
|
||||
5
Lib/encodings/__init__.py
vendored
5
Lib/encodings/__init__.py
vendored
@@ -33,6 +33,7 @@ import sys
|
||||
from . import aliases
|
||||
|
||||
_cache = {}
|
||||
_MAXCACHE = 500
|
||||
_unknown = '--unknown--'
|
||||
_import_tail = ['*']
|
||||
_aliases = aliases.aliases
|
||||
@@ -115,6 +116,8 @@ def search_function(encoding):
|
||||
|
||||
if mod is None:
|
||||
# Cache misses
|
||||
if len(_cache) >= _MAXCACHE:
|
||||
_cache.clear()
|
||||
_cache[encoding] = None
|
||||
return None
|
||||
|
||||
@@ -136,6 +139,8 @@ def search_function(encoding):
|
||||
entry = codecs.CodecInfo(*entry)
|
||||
|
||||
# Cache the codec registry entry
|
||||
if len(_cache) >= _MAXCACHE:
|
||||
_cache.clear()
|
||||
_cache[encoding] = entry
|
||||
|
||||
# Register its aliases (without overwriting previously registered
|
||||
|
||||
5
Lib/ensurepip/__init__.py
vendored
5
Lib/ensurepip/__init__.py
vendored
@@ -10,13 +10,14 @@ from shutil import copy2
|
||||
|
||||
|
||||
__all__ = ["version", "bootstrap"]
|
||||
_PIP_VERSION = "25.3"
|
||||
_PIP_VERSION = "26.1.1"
|
||||
|
||||
# Directory of system wheel packages. Some Linux distribution packaging
|
||||
# policies recommend against bundling dependencies. For example, Fedora
|
||||
# installs wheel packages in the /usr/share/python-wheels/ directory and don't
|
||||
# install the ensurepip._bundled package.
|
||||
if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None:
|
||||
_pkg_dir = sysconfig.get_config_var('WHEEL_PKG_DIR')
|
||||
if _pkg_dir:
|
||||
_WHEEL_PKG_DIR = Path(_pkg_dir).resolve()
|
||||
else:
|
||||
_WHEEL_PKG_DIR = None
|
||||
|
||||
Binary file not shown.
27
Lib/glob.py
vendored
27
Lib/glob.py
vendored
@@ -15,7 +15,7 @@ __all__ = ["glob", "iglob", "escape", "translate"]
|
||||
|
||||
def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||
include_hidden=False):
|
||||
"""Return a list of paths matching a pathname pattern.
|
||||
"""Return a list of paths matching a `pathname` pattern.
|
||||
|
||||
The pattern may contain simple shell-style wildcards a la
|
||||
fnmatch. Unlike fnmatch, filenames starting with a
|
||||
@@ -25,6 +25,15 @@ def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||
The order of the returned list is undefined. Sort it if you need a
|
||||
particular order.
|
||||
|
||||
If `root_dir` is not None, it should be a path-like object specifying the
|
||||
root directory for searching. It has the same effect as changing the
|
||||
current directory before calling it (without actually
|
||||
changing it). If pathname is relative, the result will contain
|
||||
paths relative to `root_dir`.
|
||||
|
||||
If `dir_fd` is not None, it should be a file descriptor referring to a
|
||||
directory, and paths will then be relative to that directory.
|
||||
|
||||
If `include_hidden` is true, the patterns '*', '?', '**' will match hidden
|
||||
directories.
|
||||
|
||||
@@ -36,7 +45,7 @@ def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||
|
||||
def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||
include_hidden=False):
|
||||
"""Return an iterator which yields the paths matching a pathname pattern.
|
||||
"""Return an iterator which yields the paths matching a `pathname` pattern.
|
||||
|
||||
The pattern may contain simple shell-style wildcards a la
|
||||
fnmatch. However, unlike fnmatch, filenames starting with a
|
||||
@@ -46,7 +55,19 @@ def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||
The order of the returned paths is undefined. Sort them if you need a
|
||||
particular order.
|
||||
|
||||
If recursive is true, the pattern '**' will match any files and
|
||||
If `root_dir` is not None, it should be a path-like object specifying
|
||||
the root directory for searching. It has the same effect as changing
|
||||
the current directory before calling it (without actually
|
||||
changing it). If pathname is relative, the result will contain
|
||||
paths relative to `root_dir`.
|
||||
|
||||
If `dir_fd` is not None, it should be a file descriptor referring to a
|
||||
directory, and paths will then be relative to that directory.
|
||||
|
||||
If `include_hidden` is true, the patterns '*', '?', '**' will match hidden
|
||||
directories.
|
||||
|
||||
If `recursive` is true, the pattern '**' will match any files and
|
||||
zero or more directories and subdirectories.
|
||||
"""
|
||||
sys.audit("glob.glob", pathname, recursive)
|
||||
|
||||
11
Lib/http/client.py
vendored
11
Lib/http/client.py
vendored
@@ -972,13 +972,22 @@ class HTTPConnection:
|
||||
return ip
|
||||
|
||||
def _tunnel(self):
|
||||
if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
|
||||
raise ValueError('Tunnel host can\'t contain control characters %r'
|
||||
% (self._tunnel_host,))
|
||||
connect = b"CONNECT %s:%d %s\r\n" % (
|
||||
self._wrap_ipv6(self._tunnel_host.encode("idna")),
|
||||
self._tunnel_port,
|
||||
self._http_vsn_str.encode("ascii"))
|
||||
headers = [connect]
|
||||
for header, value in self._tunnel_headers.items():
|
||||
headers.append(f"{header}: {value}\r\n".encode("latin-1"))
|
||||
header_bytes = header.encode("latin-1")
|
||||
value_bytes = value.encode("latin-1")
|
||||
if not _is_legal_header_name(header_bytes):
|
||||
raise ValueError('Invalid header name %r' % (header_bytes,))
|
||||
if _is_illegal_header_value(value_bytes):
|
||||
raise ValueError('Invalid header value %r' % (value_bytes,))
|
||||
headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
|
||||
headers.append(b"\r\n")
|
||||
# Making a single send() call instead of one per line encourages
|
||||
# the host OS to use a more optimal packet size instead of
|
||||
|
||||
30
Lib/http/cookies.py
vendored
30
Lib/http/cookies.py
vendored
@@ -337,9 +337,16 @@ class Morsel(dict):
|
||||
key = key.lower()
|
||||
if key not in self._reserved:
|
||||
raise CookieError("Invalid attribute %r" % (key,))
|
||||
if _has_control_character(key, val):
|
||||
raise CookieError("Control characters are not allowed in "
|
||||
f"cookies {key!r} {val!r}")
|
||||
data[key] = val
|
||||
dict.update(self, data)
|
||||
|
||||
def __ior__(self, values):
|
||||
self.update(values)
|
||||
return self
|
||||
|
||||
def isReservedKey(self, K):
|
||||
return K.lower() in self._reserved
|
||||
|
||||
@@ -365,9 +372,15 @@ class Morsel(dict):
|
||||
}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self._key = state['key']
|
||||
self._value = state['value']
|
||||
self._coded_value = state['coded_value']
|
||||
key = state['key']
|
||||
value = state['value']
|
||||
coded_value = state['coded_value']
|
||||
if _has_control_character(key, value, coded_value):
|
||||
raise CookieError("Control characters are not allowed in cookies "
|
||||
f"{key!r} {value!r} {coded_value!r}")
|
||||
self._key = key
|
||||
self._value = value
|
||||
self._coded_value = coded_value
|
||||
|
||||
def output(self, attrs=None, header="Set-Cookie:"):
|
||||
return "%s %s" % (header, self.OutputString(attrs))
|
||||
@@ -378,14 +391,21 @@ class Morsel(dict):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
|
||||
|
||||
def js_output(self, attrs=None):
|
||||
import base64
|
||||
# Print javascript
|
||||
output_string = self.OutputString(attrs)
|
||||
if _has_control_character(output_string):
|
||||
raise CookieError("Control characters are not allowed in cookies")
|
||||
# Base64-encode value to avoid template
|
||||
# injection in cookie values.
|
||||
output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii")
|
||||
return """
|
||||
<script type="text/javascript">
|
||||
<!-- begin hiding
|
||||
document.cookie = \"%s\";
|
||||
document.cookie = atob(\"%s\");
|
||||
// end hiding -->
|
||||
</script>
|
||||
""" % (self.OutputString(attrs).replace('"', r'\"'))
|
||||
""" % (output_encoded,)
|
||||
|
||||
def OutputString(self, attrs=None):
|
||||
# Build up our result
|
||||
|
||||
5
Lib/inspect.py
vendored
5
Lib/inspect.py
vendored
@@ -1,7 +1,7 @@
|
||||
"""Get useful information from live Python objects.
|
||||
|
||||
This module encapsulates the interface provided by the internal special
|
||||
attributes (co_*, im_*, tb_*, etc.) in a friendlier fashion.
|
||||
attributes (co_*, tb_*, etc.) in a friendlier fashion.
|
||||
It also provides some help for examining source code and class layout.
|
||||
|
||||
Here are some of the useful functions provided by this module:
|
||||
@@ -2660,11 +2660,12 @@ class Parameter:
|
||||
The annotation for the parameter if specified. If the
|
||||
parameter has no annotation, this attribute is set to
|
||||
`Parameter.empty`.
|
||||
* kind : str
|
||||
* kind
|
||||
Describes how argument values are bound to the parameter.
|
||||
Possible values: `Parameter.POSITIONAL_ONLY`,
|
||||
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
|
||||
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
|
||||
Every value has a `description` attribute describing meaning.
|
||||
"""
|
||||
|
||||
__slots__ = ('_name', '_kind', '_default', '_annotation')
|
||||
|
||||
39
Lib/logging/__init__.py
vendored
39
Lib/logging/__init__.py
vendored
@@ -1475,8 +1475,6 @@ class Logger(Filterer):
|
||||
level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.
|
||||
There is no arbitrary limit to the depth of nesting.
|
||||
"""
|
||||
_tls = threading.local()
|
||||
|
||||
def __init__(self, name, level=NOTSET):
|
||||
"""
|
||||
Initialize the logger with a name and an optional level.
|
||||
@@ -1673,19 +1671,14 @@ class Logger(Filterer):
|
||||
This method is used for unpickled records received from a socket, as
|
||||
well as those created locally. Logger-level filtering is applied.
|
||||
"""
|
||||
if self._is_disabled():
|
||||
if self.disabled:
|
||||
return
|
||||
|
||||
self._tls.in_progress = True
|
||||
try:
|
||||
maybe_record = self.filter(record)
|
||||
if not maybe_record:
|
||||
return
|
||||
if isinstance(maybe_record, LogRecord):
|
||||
record = maybe_record
|
||||
self.callHandlers(record)
|
||||
finally:
|
||||
self._tls.in_progress = False
|
||||
maybe_record = self.filter(record)
|
||||
if not maybe_record:
|
||||
return
|
||||
if isinstance(maybe_record, LogRecord):
|
||||
record = maybe_record
|
||||
self.callHandlers(record)
|
||||
|
||||
def addHandler(self, hdlr):
|
||||
"""
|
||||
@@ -1773,7 +1766,7 @@ class Logger(Filterer):
|
||||
"""
|
||||
Is this logger enabled for level 'level'?
|
||||
"""
|
||||
if self._is_disabled():
|
||||
if self.disabled:
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -1823,11 +1816,6 @@ class Logger(Filterer):
|
||||
if isinstance(item, Logger) and item.parent is self and
|
||||
_hierlevel(item) == 1 + _hierlevel(item.parent))
|
||||
|
||||
def _is_disabled(self):
|
||||
# We need to use getattr as it will only be set the first time a log
|
||||
# message is recorded on any given thread
|
||||
return self.disabled or getattr(self._tls, 'in_progress', False)
|
||||
|
||||
def __repr__(self):
|
||||
level = getLevelName(self.getEffectiveLevel())
|
||||
return '<%s %s (%s)>' % (self.__class__.__name__, self.name, level)
|
||||
@@ -1864,9 +1852,9 @@ class LoggerAdapter(object):
|
||||
|
||||
def __init__(self, logger, extra=None, merge_extra=False):
|
||||
"""
|
||||
Initialize the adapter with a logger and a dict-like object which
|
||||
provides contextual information. This constructor signature allows
|
||||
easy stacking of LoggerAdapters, if so desired.
|
||||
Initialize the adapter with a logger and an optional dict-like object
|
||||
which provides contextual information. This constructor signature
|
||||
allows easy stacking of LoggerAdapters, if so desired.
|
||||
|
||||
You can effectively pass keyword arguments as shown in the
|
||||
following example:
|
||||
@@ -1897,8 +1885,9 @@ class LoggerAdapter(object):
|
||||
Normally, you'll only need to override this one method in a
|
||||
LoggerAdapter subclass for your specific needs.
|
||||
"""
|
||||
if self.merge_extra and "extra" in kwargs:
|
||||
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
|
||||
if self.merge_extra and kwargs.get("extra") is not None:
|
||||
if self.extra is not None:
|
||||
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
|
||||
else:
|
||||
kwargs["extra"] = self.extra
|
||||
return msg, kwargs
|
||||
|
||||
14
Lib/logging/config.py
vendored
14
Lib/logging/config.py
vendored
@@ -865,6 +865,8 @@ class DictConfigurator(BaseConfigurator):
|
||||
else:
|
||||
factory = klass
|
||||
kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
|
||||
# When deprecation ends for using the 'strm' parameter, remove the
|
||||
# "except TypeError ..."
|
||||
try:
|
||||
result = factory(**kwargs)
|
||||
except TypeError as te:
|
||||
@@ -876,6 +878,15 @@ class DictConfigurator(BaseConfigurator):
|
||||
#(e.g. by Django)
|
||||
kwargs['strm'] = kwargs.pop('stream')
|
||||
result = factory(**kwargs)
|
||||
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"Support for custom logging handlers with the 'strm' argument "
|
||||
"is deprecated and scheduled for removal in Python 3.16. "
|
||||
"Define handlers with the 'stream' argument instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if formatter:
|
||||
result.setFormatter(formatter)
|
||||
if level is not None:
|
||||
@@ -1006,7 +1017,8 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
|
||||
A simple TCP socket-based logging config receiver.
|
||||
"""
|
||||
|
||||
allow_reuse_address = 1
|
||||
allow_reuse_address = True
|
||||
allow_reuse_port = False
|
||||
|
||||
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
|
||||
handler=None, ready=None, verify=None):
|
||||
|
||||
24
Lib/logging/handlers.py
vendored
24
Lib/logging/handlers.py
vendored
@@ -196,7 +196,11 @@ class RotatingFileHandler(BaseRotatingHandler):
|
||||
if self.stream is None: # delay was set...
|
||||
self.stream = self._open()
|
||||
if self.maxBytes > 0: # are we rolling over?
|
||||
pos = self.stream.tell()
|
||||
try:
|
||||
pos = self.stream.tell()
|
||||
except io.UnsupportedOperation:
|
||||
# gh-143237: Never rollover a named pipe.
|
||||
return False
|
||||
if not pos:
|
||||
# gh-116263: Never rollover an empty file
|
||||
return False
|
||||
@@ -855,7 +859,7 @@ class SysLogHandler(logging.Handler):
|
||||
}
|
||||
|
||||
def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
|
||||
facility=LOG_USER, socktype=None):
|
||||
facility=LOG_USER, socktype=None, timeout=None):
|
||||
"""
|
||||
Initialize a handler.
|
||||
|
||||
@@ -872,6 +876,7 @@ class SysLogHandler(logging.Handler):
|
||||
self.address = address
|
||||
self.facility = facility
|
||||
self.socktype = socktype
|
||||
self.timeout = timeout
|
||||
self.socket = None
|
||||
self.createSocket()
|
||||
|
||||
@@ -933,6 +938,8 @@ class SysLogHandler(logging.Handler):
|
||||
err = sock = None
|
||||
try:
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
if self.timeout:
|
||||
sock.settimeout(self.timeout)
|
||||
if socktype == socket.SOCK_STREAM:
|
||||
sock.connect(sa)
|
||||
break
|
||||
@@ -1529,6 +1536,19 @@ class QueueListener(object):
|
||||
self._thread = None
|
||||
self.respect_handler_level = respect_handler_level
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
For use as a context manager. Starts the listener.
|
||||
"""
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
"""
|
||||
For use as a context manager. Stops the listener.
|
||||
"""
|
||||
self.stop()
|
||||
|
||||
def dequeue(self, block):
|
||||
"""
|
||||
Dequeue a record and return it, optionally blocking.
|
||||
|
||||
12
Lib/pickle.py
vendored
12
Lib/pickle.py
vendored
@@ -904,17 +904,11 @@ class _Pickler:
|
||||
# Write data in-band
|
||||
# XXX The C implementation avoids a copy here
|
||||
buf = m.tobytes()
|
||||
in_memo = id(buf) in self.memo
|
||||
if m.readonly:
|
||||
if in_memo:
|
||||
self._save_bytes_no_memo(buf)
|
||||
else:
|
||||
self.save_bytes(buf)
|
||||
self._save_bytes_no_memo(buf)
|
||||
else:
|
||||
if in_memo:
|
||||
self._save_bytearray_no_memo(buf)
|
||||
else:
|
||||
self.save_bytearray(buf)
|
||||
self._save_bytearray_no_memo(buf)
|
||||
self.memoize(obj)
|
||||
else:
|
||||
# Write data out-of-band
|
||||
self.write(NEXT_BUFFER)
|
||||
|
||||
117
Lib/platform.py
vendored
Executable file → Normal file
117
Lib/platform.py
vendored
Executable file → Normal file
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
""" This module tries to retrieve as much platform-identifying data as
|
||||
possible. It makes this information available via function APIs.
|
||||
|
||||
@@ -33,6 +31,7 @@
|
||||
#
|
||||
# <see CVS and SVN checkin messages for history>
|
||||
#
|
||||
# 1.0.9 - added invalidate_caches() function to invalidate cached values
|
||||
# 1.0.8 - changed Windows support to read version from kernel32.dll
|
||||
# 1.0.7 - added DEV_NULL
|
||||
# 1.0.6 - added linux_distribution()
|
||||
@@ -111,7 +110,7 @@ __copyright__ = """
|
||||
|
||||
"""
|
||||
|
||||
__version__ = '1.0.8'
|
||||
__version__ = '1.0.9'
|
||||
|
||||
import collections
|
||||
import os
|
||||
@@ -174,6 +173,11 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
|
||||
|
||||
"""
|
||||
if not executable:
|
||||
if sys.platform == "emscripten":
|
||||
# Emscripten's os.confstr reports that it is glibc, so special case
|
||||
# it.
|
||||
ver = ".".join(str(x) for x in sys._emscripten_info.emscripten_version)
|
||||
return ("emscripten", ver)
|
||||
try:
|
||||
ver = os.confstr('CS_GNU_LIBC_VERSION')
|
||||
# parse 'glibc 2.28' as ('glibc', '2.28')
|
||||
@@ -190,22 +194,26 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
|
||||
# sys.executable is not set.
|
||||
return lib, version
|
||||
|
||||
libc_search = re.compile(b'(__libc_init)'
|
||||
b'|'
|
||||
b'(GLIBC_([0-9.]+))'
|
||||
b'|'
|
||||
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
|
||||
libc_search = re.compile(br"""
|
||||
(__libc_init)
|
||||
| (GLIBC_([0-9.]+))
|
||||
| (libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)
|
||||
| (musl-([0-9.]+))
|
||||
| ((?:libc\.|ld-)musl(?:-\w+)?.so(?:\.(\d[0-9.]*))?)
|
||||
""",
|
||||
re.ASCII | re.VERBOSE)
|
||||
|
||||
V = _comparable_version
|
||||
# We use os.path.realpath()
|
||||
# here to work around problems with Cygwin not being
|
||||
# able to open symlinks for reading
|
||||
executable = os.path.realpath(executable)
|
||||
ver = None
|
||||
with open(executable, 'rb') as f:
|
||||
binary = f.read(chunksize)
|
||||
pos = 0
|
||||
while pos < len(binary):
|
||||
if b'libc' in binary or b'GLIBC' in binary:
|
||||
if b'libc' in binary or b'GLIBC' in binary or b'musl' in binary:
|
||||
m = libc_search.search(binary, pos)
|
||||
else:
|
||||
m = None
|
||||
@@ -217,26 +225,35 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
|
||||
continue
|
||||
if not m:
|
||||
break
|
||||
libcinit, glibc, glibcversion, so, threads, soversion = [
|
||||
s.decode('latin1') if s is not None else s
|
||||
for s in m.groups()]
|
||||
decoded_groups = [s.decode('latin1') if s is not None else s
|
||||
for s in m.groups()]
|
||||
(libcinit, glibc, glibcversion, so, threads, soversion,
|
||||
musl, muslversion, musl_so, musl_sover) = decoded_groups
|
||||
if libcinit and not lib:
|
||||
lib = 'libc'
|
||||
elif glibc:
|
||||
if lib != 'glibc':
|
||||
lib = 'glibc'
|
||||
version = glibcversion
|
||||
elif V(glibcversion) > V(version):
|
||||
version = glibcversion
|
||||
ver = glibcversion
|
||||
elif V(glibcversion) > V(ver):
|
||||
ver = glibcversion
|
||||
elif so:
|
||||
if lib != 'glibc':
|
||||
if lib not in ('glibc', 'musl'):
|
||||
lib = 'libc'
|
||||
if soversion and (not version or V(soversion) > V(version)):
|
||||
version = soversion
|
||||
if threads and version[-len(threads):] != threads:
|
||||
version = version + threads
|
||||
if soversion and (not ver or V(soversion) > V(ver)):
|
||||
ver = soversion
|
||||
if threads and ver[-len(threads):] != threads:
|
||||
ver = ver + threads
|
||||
elif musl:
|
||||
lib = 'musl'
|
||||
if not ver or V(muslversion) > V(ver):
|
||||
ver = muslversion
|
||||
elif musl_so:
|
||||
lib = 'musl'
|
||||
if musl_sover and (not ver or V(musl_sover) > V(ver)):
|
||||
ver = musl_sover
|
||||
pos = m.end()
|
||||
return lib, version
|
||||
return lib, version if ver is None else ver
|
||||
|
||||
def _norm_version(version, build=''):
|
||||
|
||||
@@ -549,7 +566,7 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
|
||||
warnings._deprecated('java_ver', remove=(3, 15))
|
||||
# Import the needed APIs
|
||||
try:
|
||||
import java.lang
|
||||
import java.lang # noqa: F401
|
||||
except ImportError:
|
||||
return release, vendor, vminfo, osinfo
|
||||
|
||||
@@ -1192,7 +1209,7 @@ def _sys_version(sys_version=None):
|
||||
# CPython
|
||||
cpython_sys_version_parser = re.compile(
|
||||
r'([\w.+]+)\s*' # "version<space>"
|
||||
r'(?:experimental free-threading build\s+)?' # "free-threading-build<space>"
|
||||
r'(?:free-threading build\s+)?' # "free-threading-build<space>"
|
||||
r'\(#?([^,]+)' # "(#buildno"
|
||||
r'(?:,\s*([\w ]*)' # ", builddate"
|
||||
r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
|
||||
@@ -1449,11 +1466,55 @@ def freedesktop_os_release():
|
||||
return _os_release_cache.copy()
|
||||
|
||||
|
||||
def invalidate_caches():
|
||||
"""Invalidate the cached results."""
|
||||
global _uname_cache
|
||||
_uname_cache = None
|
||||
|
||||
global _os_release_cache
|
||||
_os_release_cache = None
|
||||
|
||||
_sys_version_cache.clear()
|
||||
_platform_cache.clear()
|
||||
|
||||
|
||||
### Command line interface
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Default is to print the aliased verbose platform string
|
||||
terse = ('terse' in sys.argv or '--terse' in sys.argv)
|
||||
aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
|
||||
def _parse_args(args: list[str] | None):
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(color=True)
|
||||
parser.add_argument("args", nargs="*", choices=["nonaliased", "terse"])
|
||||
parser.add_argument(
|
||||
"--terse",
|
||||
action="store_true",
|
||||
help=(
|
||||
"return only the absolute minimum information needed "
|
||||
"to identify the platform"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nonaliased",
|
||||
dest="aliased",
|
||||
action="store_false",
|
||||
help=(
|
||||
"disable system/OS name aliasing. If aliasing is enabled, "
|
||||
"some platforms report system names different from "
|
||||
"their common names, e.g. SunOS is reported as Solaris"
|
||||
),
|
||||
)
|
||||
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
def _main(args: list[str] | None = None):
|
||||
args = _parse_args(args)
|
||||
|
||||
terse = args.terse or ("terse" in args.args)
|
||||
aliased = args.aliased and ('nonaliased' not in args.args)
|
||||
|
||||
print(platform(aliased, terse))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
||||
|
||||
6
Lib/plistlib.py
vendored
6
Lib/plistlib.py
vendored
@@ -21,7 +21,7 @@ datetime.datetime objects.
|
||||
|
||||
Generate Plist example:
|
||||
|
||||
import datetime
|
||||
import datetime as dt
|
||||
import plistlib
|
||||
|
||||
pl = dict(
|
||||
@@ -37,7 +37,7 @@ Generate Plist example:
|
||||
),
|
||||
someData = b"<binary gunk>",
|
||||
someMoreData = b"<lots of binary gunk>" * 10,
|
||||
aDate = datetime.datetime.now()
|
||||
aDate = dt.datetime.now()
|
||||
)
|
||||
print(plistlib.dumps(pl).decode())
|
||||
|
||||
@@ -384,7 +384,7 @@ class _PlistWriter(_DumbXMLWriter):
|
||||
self._indent_level -= 1
|
||||
maxlinelength = max(
|
||||
16,
|
||||
76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level))
|
||||
76 - len((self.indent * self._indent_level).expandtabs()))
|
||||
|
||||
for line in _encode_base64(data, maxlinelength).split(b"\n"):
|
||||
if line:
|
||||
|
||||
615
Lib/profile.py
vendored
Normal file
615
Lib/profile.py
vendored
Normal file
@@ -0,0 +1,615 @@
|
||||
#
|
||||
# Class for profiling python code. rev 1.0 6/2/94
|
||||
#
|
||||
# Written by James Roskind
|
||||
# Based on prior profile module by Sjoerd Mullender...
|
||||
# which was hacked somewhat by: Guido van Rossum
|
||||
|
||||
"""Class for profiling Python code."""
|
||||
|
||||
# Copyright Disney Enterprises, Inc. All Rights Reserved.
|
||||
# Licensed to PSF under a Contributor Agreement
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
# either express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
|
||||
import importlib.machinery
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
import marshal
|
||||
|
||||
__all__ = ["run", "runctx", "Profile"]
|
||||
|
||||
# Sample timer for use with
|
||||
#i_count = 0
|
||||
#def integer_timer():
|
||||
# global i_count
|
||||
# i_count = i_count + 1
|
||||
# return i_count
|
||||
#itimes = integer_timer # replace with C coded timer returning integers
|
||||
|
||||
class _Utils:
|
||||
"""Support class for utility functions which are shared by
|
||||
profile.py and cProfile.py modules.
|
||||
Not supposed to be used directly.
|
||||
"""
|
||||
|
||||
def __init__(self, profiler):
|
||||
self.profiler = profiler
|
||||
|
||||
def run(self, statement, filename, sort):
|
||||
prof = self.profiler()
|
||||
try:
|
||||
prof.run(statement)
|
||||
except SystemExit:
|
||||
pass
|
||||
finally:
|
||||
self._show(prof, filename, sort)
|
||||
|
||||
def runctx(self, statement, globals, locals, filename, sort):
|
||||
prof = self.profiler()
|
||||
try:
|
||||
prof.runctx(statement, globals, locals)
|
||||
except SystemExit:
|
||||
pass
|
||||
finally:
|
||||
self._show(prof, filename, sort)
|
||||
|
||||
def _show(self, prof, filename, sort):
|
||||
if filename is not None:
|
||||
prof.dump_stats(filename)
|
||||
else:
|
||||
prof.print_stats(sort)
|
||||
|
||||
|
||||
#**************************************************************************
|
||||
# The following are the static member functions for the profiler class
|
||||
# Note that an instance of Profile() is *not* needed to call them.
|
||||
#**************************************************************************
|
||||
|
||||
def run(statement, filename=None, sort=-1):
|
||||
"""Run statement under profiler optionally saving results in filename
|
||||
|
||||
This function takes a single argument that can be passed to the
|
||||
"exec" statement, and an optional file name. In all cases this
|
||||
routine attempts to "exec" its first argument and gather profiling
|
||||
statistics from the execution. If no file name is present, then this
|
||||
function automatically prints a simple profiling report, sorted by the
|
||||
standard name string (file/line/function-name) that is presented in
|
||||
each line.
|
||||
"""
|
||||
return _Utils(Profile).run(statement, filename, sort)
|
||||
|
||||
def runctx(statement, globals, locals, filename=None, sort=-1):
|
||||
"""Run statement under profiler, supplying your own globals and locals,
|
||||
optionally saving results in filename.
|
||||
|
||||
statement and filename have the same semantics as profile.run
|
||||
"""
|
||||
return _Utils(Profile).runctx(statement, globals, locals, filename, sort)
|
||||
|
||||
|
||||
class Profile:
|
||||
"""Profiler class.
|
||||
|
||||
self.cur is always a tuple. Each such tuple corresponds to a stack
|
||||
frame that is currently active (self.cur[-2]). The following are the
|
||||
definitions of its members. We use this external "parallel stack" to
|
||||
avoid contaminating the program that we are profiling. (old profiler
|
||||
used to write into the frames local dictionary!!) Derived classes
|
||||
can change the definition of some entries, as long as they leave
|
||||
[-2:] intact (frame and previous tuple). In case an internal error is
|
||||
detected, the -3 element is used as the function name.
|
||||
|
||||
[ 0] = Time that needs to be charged to the parent frame's function.
|
||||
It is used so that a function call will not have to access the
|
||||
timing data for the parent frame.
|
||||
[ 1] = Total time spent in this frame's function, excluding time in
|
||||
subfunctions (this latter is tallied in cur[2]).
|
||||
[ 2] = Total time spent in subfunctions, excluding time executing the
|
||||
frame's function (this latter is tallied in cur[1]).
|
||||
[-3] = Name of the function that corresponds to this frame.
|
||||
[-2] = Actual frame that we correspond to (used to sync exception handling).
|
||||
[-1] = Our parent 6-tuple (corresponds to frame.f_back).
|
||||
|
||||
Timing data for each function is stored as a 5-tuple in the dictionary
|
||||
self.timings[]. The index is always the name stored in self.cur[-3].
|
||||
The following are the definitions of the members:
|
||||
|
||||
[0] = The number of times this function was called, not counting direct
|
||||
or indirect recursion,
|
||||
[1] = Number of times this function appears on the stack, minus one
|
||||
[2] = Total time spent internal to this function
|
||||
[3] = Cumulative time that this function was present on the stack. In
|
||||
non-recursive functions, this is the total execution time from start
|
||||
to finish of each invocation of a function, including time spent in
|
||||
all subfunctions.
|
||||
[4] = A dictionary indicating for each function name, the number of times
|
||||
it was called by us.
|
||||
"""
|
||||
|
||||
bias = 0 # calibration constant
|
||||
|
||||
def __init__(self, timer=None, bias=None):
|
||||
self.timings = {}
|
||||
self.cur = None
|
||||
self.cmd = ""
|
||||
self.c_func_name = ""
|
||||
|
||||
if bias is None:
|
||||
bias = self.bias
|
||||
self.bias = bias # Materialize in local dict for lookup speed.
|
||||
|
||||
if not timer:
|
||||
self.timer = self.get_time = time.process_time
|
||||
self.dispatcher = self.trace_dispatch_i
|
||||
else:
|
||||
self.timer = timer
|
||||
t = self.timer() # test out timer function
|
||||
try:
|
||||
length = len(t)
|
||||
except TypeError:
|
||||
self.get_time = timer
|
||||
self.dispatcher = self.trace_dispatch_i
|
||||
else:
|
||||
if length == 2:
|
||||
self.dispatcher = self.trace_dispatch
|
||||
else:
|
||||
self.dispatcher = self.trace_dispatch_l
|
||||
# This get_time() implementation needs to be defined
|
||||
# here to capture the passed-in timer in the parameter
|
||||
# list (for performance). Note that we can't assume
|
||||
# the timer() result contains two values in all
|
||||
# cases.
|
||||
def get_time_timer(timer=timer, sum=sum):
|
||||
return sum(timer())
|
||||
self.get_time = get_time_timer
|
||||
self.t = self.get_time()
|
||||
self.simulate_call('profiler')
|
||||
|
||||
# Heavily optimized dispatch routine for time.process_time() timer
|
||||
|
||||
def trace_dispatch(self, frame, event, arg):
|
||||
timer = self.timer
|
||||
t = timer()
|
||||
t = t[0] + t[1] - self.t - self.bias
|
||||
|
||||
if event == "c_call":
|
||||
self.c_func_name = arg.__name__
|
||||
|
||||
if self.dispatch[event](self, frame,t):
|
||||
t = timer()
|
||||
self.t = t[0] + t[1]
|
||||
else:
|
||||
r = timer()
|
||||
self.t = r[0] + r[1] - t # put back unrecorded delta
|
||||
|
||||
# Dispatch routine for best timer program (return = scalar, fastest if
|
||||
# an integer but float works too -- and time.process_time() relies on that).
|
||||
|
||||
def trace_dispatch_i(self, frame, event, arg):
|
||||
timer = self.timer
|
||||
t = timer() - self.t - self.bias
|
||||
|
||||
if event == "c_call":
|
||||
self.c_func_name = arg.__name__
|
||||
|
||||
if self.dispatch[event](self, frame, t):
|
||||
self.t = timer()
|
||||
else:
|
||||
self.t = timer() - t # put back unrecorded delta
|
||||
|
||||
# Dispatch routine for macintosh (timer returns time in ticks of
|
||||
# 1/60th second)
|
||||
|
||||
def trace_dispatch_mac(self, frame, event, arg):
|
||||
timer = self.timer
|
||||
t = timer()/60.0 - self.t - self.bias
|
||||
|
||||
if event == "c_call":
|
||||
self.c_func_name = arg.__name__
|
||||
|
||||
if self.dispatch[event](self, frame, t):
|
||||
self.t = timer()/60.0
|
||||
else:
|
||||
self.t = timer()/60.0 - t # put back unrecorded delta
|
||||
|
||||
# SLOW generic dispatch routine for timer returning lists of numbers
|
||||
|
||||
def trace_dispatch_l(self, frame, event, arg):
|
||||
get_time = self.get_time
|
||||
t = get_time() - self.t - self.bias
|
||||
|
||||
if event == "c_call":
|
||||
self.c_func_name = arg.__name__
|
||||
|
||||
if self.dispatch[event](self, frame, t):
|
||||
self.t = get_time()
|
||||
else:
|
||||
self.t = get_time() - t # put back unrecorded delta
|
||||
|
||||
# In the event handlers, the first 3 elements of self.cur are unpacked
|
||||
# into vrbls w/ 3-letter names. The last two characters are meant to be
|
||||
# mnemonic:
|
||||
# _pt self.cur[0] "parent time" time to be charged to parent frame
|
||||
# _it self.cur[1] "internal time" time spent directly in the function
|
||||
# _et self.cur[2] "external time" time spent in subfunctions
|
||||
|
||||
def trace_dispatch_exception(self, frame, t):
|
||||
rpt, rit, ret, rfn, rframe, rcur = self.cur
|
||||
if (rframe is not frame) and rcur:
|
||||
return self.trace_dispatch_return(rframe, t)
|
||||
self.cur = rpt, rit+t, ret, rfn, rframe, rcur
|
||||
return 1
|
||||
|
||||
|
||||
def trace_dispatch_call(self, frame, t):
|
||||
if self.cur and frame.f_back is not self.cur[-2]:
|
||||
rpt, rit, ret, rfn, rframe, rcur = self.cur
|
||||
if not isinstance(rframe, Profile.fake_frame):
|
||||
assert rframe.f_back is frame.f_back, ("Bad call", rfn,
|
||||
rframe, rframe.f_back,
|
||||
frame, frame.f_back)
|
||||
self.trace_dispatch_return(rframe, 0)
|
||||
assert (self.cur is None or \
|
||||
frame.f_back is self.cur[-2]), ("Bad call",
|
||||
self.cur[-3])
|
||||
fcode = frame.f_code
|
||||
fn = (fcode.co_filename, fcode.co_firstlineno, fcode.co_name)
|
||||
self.cur = (t, 0, 0, fn, frame, self.cur)
|
||||
timings = self.timings
|
||||
if fn in timings:
|
||||
cc, ns, tt, ct, callers = timings[fn]
|
||||
timings[fn] = cc, ns + 1, tt, ct, callers
|
||||
else:
|
||||
timings[fn] = 0, 0, 0, 0, {}
|
||||
return 1
|
||||
|
||||
def trace_dispatch_c_call (self, frame, t):
|
||||
fn = ("", 0, self.c_func_name)
|
||||
self.cur = (t, 0, 0, fn, frame, self.cur)
|
||||
timings = self.timings
|
||||
if fn in timings:
|
||||
cc, ns, tt, ct, callers = timings[fn]
|
||||
timings[fn] = cc, ns+1, tt, ct, callers
|
||||
else:
|
||||
timings[fn] = 0, 0, 0, 0, {}
|
||||
return 1
|
||||
|
||||
def trace_dispatch_return(self, frame, t):
|
||||
if frame is not self.cur[-2]:
|
||||
assert frame is self.cur[-2].f_back, ("Bad return", self.cur[-3])
|
||||
self.trace_dispatch_return(self.cur[-2], 0)
|
||||
|
||||
# Prefix "r" means part of the Returning or exiting frame.
|
||||
# Prefix "p" means part of the Previous or Parent or older frame.
|
||||
|
||||
rpt, rit, ret, rfn, frame, rcur = self.cur
|
||||
rit = rit + t
|
||||
frame_total = rit + ret
|
||||
|
||||
ppt, pit, pet, pfn, pframe, pcur = rcur
|
||||
self.cur = ppt, pit + rpt, pet + frame_total, pfn, pframe, pcur
|
||||
|
||||
timings = self.timings
|
||||
cc, ns, tt, ct, callers = timings[rfn]
|
||||
if not ns:
|
||||
# This is the only occurrence of the function on the stack.
|
||||
# Else this is a (directly or indirectly) recursive call, and
|
||||
# its cumulative time will get updated when the topmost call to
|
||||
# it returns.
|
||||
ct = ct + frame_total
|
||||
cc = cc + 1
|
||||
|
||||
if pfn in callers:
|
||||
callers[pfn] = callers[pfn] + 1 # hack: gather more
|
||||
# stats such as the amount of time added to ct courtesy
|
||||
# of this specific call, and the contribution to cc
|
||||
# courtesy of this call.
|
||||
else:
|
||||
callers[pfn] = 1
|
||||
|
||||
timings[rfn] = cc, ns - 1, tt + rit, ct, callers
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
dispatch = {
|
||||
"call": trace_dispatch_call,
|
||||
"exception": trace_dispatch_exception,
|
||||
"return": trace_dispatch_return,
|
||||
"c_call": trace_dispatch_c_call,
|
||||
"c_exception": trace_dispatch_return, # the C function returned
|
||||
"c_return": trace_dispatch_return,
|
||||
}
|
||||
|
||||
|
||||
# The next few functions play with self.cmd. By carefully preloading
|
||||
# our parallel stack, we can force the profiled result to include
|
||||
# an arbitrary string as the name of the calling function.
|
||||
# We use self.cmd as that string, and the resulting stats look
|
||||
# very nice :-).
|
||||
|
||||
def set_cmd(self, cmd):
|
||||
if self.cur[-1]: return # already set
|
||||
self.cmd = cmd
|
||||
self.simulate_call(cmd)
|
||||
|
||||
class fake_code:
|
||||
def __init__(self, filename, line, name):
|
||||
self.co_filename = filename
|
||||
self.co_line = line
|
||||
self.co_name = name
|
||||
self.co_firstlineno = 0
|
||||
|
||||
def __repr__(self):
|
||||
return repr((self.co_filename, self.co_line, self.co_name))
|
||||
|
||||
class fake_frame:
|
||||
def __init__(self, code, prior):
|
||||
self.f_code = code
|
||||
self.f_back = prior
|
||||
|
||||
def simulate_call(self, name):
|
||||
code = self.fake_code('profile', 0, name)
|
||||
if self.cur:
|
||||
pframe = self.cur[-2]
|
||||
else:
|
||||
pframe = None
|
||||
frame = self.fake_frame(code, pframe)
|
||||
self.dispatch['call'](self, frame, 0)
|
||||
|
||||
# collect stats from pending stack, including getting final
|
||||
# timings for self.cmd frame.
|
||||
|
||||
def simulate_cmd_complete(self):
|
||||
get_time = self.get_time
|
||||
t = get_time() - self.t
|
||||
while self.cur[-1]:
|
||||
# We *can* cause assertion errors here if
|
||||
# dispatch_trace_return checks for a frame match!
|
||||
self.dispatch['return'](self, self.cur[-2], t)
|
||||
t = 0
|
||||
self.t = get_time() - t
|
||||
|
||||
|
||||
def print_stats(self, sort=-1):
|
||||
import pstats
|
||||
if not isinstance(sort, tuple):
|
||||
sort = (sort,)
|
||||
pstats.Stats(self).strip_dirs().sort_stats(*sort).print_stats()
|
||||
|
||||
def dump_stats(self, file):
|
||||
with open(file, 'wb') as f:
|
||||
self.create_stats()
|
||||
marshal.dump(self.stats, f)
|
||||
|
||||
def create_stats(self):
|
||||
self.simulate_cmd_complete()
|
||||
self.snapshot_stats()
|
||||
|
||||
def snapshot_stats(self):
|
||||
self.stats = {}
|
||||
for func, (cc, ns, tt, ct, callers) in self.timings.items():
|
||||
callers = callers.copy()
|
||||
nc = 0
|
||||
for callcnt in callers.values():
|
||||
nc += callcnt
|
||||
self.stats[func] = cc, nc, tt, ct, callers
|
||||
|
||||
|
||||
# The following two methods can be called by clients to use
|
||||
# a profiler to profile a statement, given as a string.
|
||||
|
||||
def run(self, cmd):
|
||||
import __main__
|
||||
dict = __main__.__dict__
|
||||
return self.runctx(cmd, dict, dict)
|
||||
|
||||
def runctx(self, cmd, globals, locals):
|
||||
self.set_cmd(cmd)
|
||||
sys.setprofile(self.dispatcher)
|
||||
try:
|
||||
exec(cmd, globals, locals)
|
||||
finally:
|
||||
sys.setprofile(None)
|
||||
return self
|
||||
|
||||
# This method is more useful to profile a single function call.
|
||||
def runcall(self, func, /, *args, **kw):
|
||||
self.set_cmd(repr(func))
|
||||
sys.setprofile(self.dispatcher)
|
||||
try:
|
||||
return func(*args, **kw)
|
||||
finally:
|
||||
sys.setprofile(None)
|
||||
|
||||
|
||||
#******************************************************************
|
||||
# The following calculates the overhead for using a profiler. The
|
||||
# problem is that it takes a fair amount of time for the profiler
|
||||
# to stop the stopwatch (from the time it receives an event).
|
||||
# Similarly, there is a delay from the time that the profiler
|
||||
# re-starts the stopwatch before the user's code really gets to
|
||||
# continue. The following code tries to measure the difference on
|
||||
# a per-event basis.
|
||||
#
|
||||
# Note that this difference is only significant if there are a lot of
|
||||
# events, and relatively little user code per event. For example,
|
||||
# code with small functions will typically benefit from having the
|
||||
# profiler calibrated for the current platform. This *could* be
|
||||
# done on the fly during init() time, but it is not worth the
|
||||
# effort. Also note that if too large a value specified, then
|
||||
# execution time on some functions will actually appear as a
|
||||
# negative number. It is *normal* for some functions (with very
|
||||
# low call counts) to have such negative stats, even if the
|
||||
# calibration figure is "correct."
|
||||
#
|
||||
# One alternative to profile-time calibration adjustments (i.e.,
|
||||
# adding in the magic little delta during each event) is to track
|
||||
# more carefully the number of events (and cumulatively, the number
|
||||
# of events during sub functions) that are seen. If this were
|
||||
# done, then the arithmetic could be done after the fact (i.e., at
|
||||
# display time). Currently, we track only call/return events.
|
||||
# These values can be deduced by examining the callees and callers
|
||||
# vectors for each functions. Hence we *can* almost correct the
|
||||
# internal time figure at print time (note that we currently don't
|
||||
# track exception event processing counts). Unfortunately, there
|
||||
# is currently no similar information for cumulative sub-function
|
||||
# time. It would not be hard to "get all this info" at profiler
|
||||
# time. Specifically, we would have to extend the tuples to keep
|
||||
# counts of this in each frame, and then extend the defs of timing
|
||||
# tuples to include the significant two figures. I'm a bit fearful
|
||||
# that this additional feature will slow the heavily optimized
|
||||
# event/time ratio (i.e., the profiler would run slower, fur a very
|
||||
# low "value added" feature.)
|
||||
#**************************************************************
|
||||
|
||||
def calibrate(self, m, verbose=0):
|
||||
if self.__class__ is not Profile:
|
||||
raise TypeError("Subclasses must override .calibrate().")
|
||||
|
||||
saved_bias = self.bias
|
||||
self.bias = 0
|
||||
try:
|
||||
return self._calibrate_inner(m, verbose)
|
||||
finally:
|
||||
self.bias = saved_bias
|
||||
|
||||
def _calibrate_inner(self, m, verbose):
|
||||
get_time = self.get_time
|
||||
|
||||
# Set up a test case to be run with and without profiling. Include
|
||||
# lots of calls, because we're trying to quantify stopwatch overhead.
|
||||
# Do not raise any exceptions, though, because we want to know
|
||||
# exactly how many profile events are generated (one call event, +
|
||||
# one return event, per Python-level call).
|
||||
|
||||
def f1(n):
|
||||
for i in range(n):
|
||||
x = 1
|
||||
|
||||
def f(m, f1=f1):
|
||||
for i in range(m):
|
||||
f1(100)
|
||||
|
||||
f(m) # warm up the cache
|
||||
|
||||
# elapsed_noprofile <- time f(m) takes without profiling.
|
||||
t0 = get_time()
|
||||
f(m)
|
||||
t1 = get_time()
|
||||
elapsed_noprofile = t1 - t0
|
||||
if verbose:
|
||||
print("elapsed time without profiling =", elapsed_noprofile)
|
||||
|
||||
# elapsed_profile <- time f(m) takes with profiling. The difference
|
||||
# is profiling overhead, only some of which the profiler subtracts
|
||||
# out on its own.
|
||||
p = Profile()
|
||||
t0 = get_time()
|
||||
p.runctx('f(m)', globals(), locals())
|
||||
t1 = get_time()
|
||||
elapsed_profile = t1 - t0
|
||||
if verbose:
|
||||
print("elapsed time with profiling =", elapsed_profile)
|
||||
|
||||
# reported_time <- "CPU seconds" the profiler charged to f and f1.
|
||||
total_calls = 0.0
|
||||
reported_time = 0.0
|
||||
for (filename, line, funcname), (cc, ns, tt, ct, callers) in \
|
||||
p.timings.items():
|
||||
if funcname in ("f", "f1"):
|
||||
total_calls += cc
|
||||
reported_time += tt
|
||||
|
||||
if verbose:
|
||||
print("'CPU seconds' profiler reported =", reported_time)
|
||||
print("total # calls =", total_calls)
|
||||
if total_calls != m + 1:
|
||||
raise ValueError("internal error: total calls = %d" % total_calls)
|
||||
|
||||
# reported_time - elapsed_noprofile = overhead the profiler wasn't
|
||||
# able to measure. Divide by twice the number of calls (since there
|
||||
# are two profiler events per call in this test) to get the hidden
|
||||
# overhead per event.
|
||||
mean = (reported_time - elapsed_noprofile) / 2.0 / total_calls
|
||||
if verbose:
|
||||
print("mean stopwatch overhead per profile event =", mean)
|
||||
return mean
|
||||
|
||||
#****************************************************************************
|
||||
|
||||
def main():
|
||||
import os
|
||||
from optparse import OptionParser
|
||||
|
||||
usage = "profile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.allow_interspersed_args = False
|
||||
parser.add_option('-o', '--outfile', dest="outfile",
|
||||
help="Save stats to <outfile>", default=None)
|
||||
parser.add_option('-m', dest="module", action="store_true",
|
||||
help="Profile a library module.", default=False)
|
||||
parser.add_option('-s', '--sort', dest="sort",
|
||||
help="Sort order when printing to stdout, based on pstats.Stats class",
|
||||
default=-1)
|
||||
|
||||
if not sys.argv[1:]:
|
||||
parser.print_usage()
|
||||
sys.exit(2)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
sys.argv[:] = args
|
||||
|
||||
# The script that we're profiling may chdir, so capture the absolute path
|
||||
# to the output file at startup.
|
||||
if options.outfile is not None:
|
||||
options.outfile = os.path.abspath(options.outfile)
|
||||
|
||||
if len(args) > 0:
|
||||
if options.module:
|
||||
import runpy
|
||||
code = "run_module(modname, run_name='__main__')"
|
||||
globs = {
|
||||
'run_module': runpy.run_module,
|
||||
'modname': args[0]
|
||||
}
|
||||
else:
|
||||
progname = args[0]
|
||||
sys.path.insert(0, os.path.dirname(progname))
|
||||
with io.open_code(progname) as fp:
|
||||
code = compile(fp.read(), progname, 'exec')
|
||||
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
|
||||
origin=progname)
|
||||
globs = {
|
||||
'__spec__': spec,
|
||||
'__file__': spec.origin,
|
||||
'__name__': spec.name,
|
||||
'__package__': None,
|
||||
'__cached__': None,
|
||||
}
|
||||
try:
|
||||
runctx(code, globs, None, options.outfile, options.sort)
|
||||
except BrokenPipeError as exc:
|
||||
# Prevent "Exception ignored" during interpreter shutdown.
|
||||
sys.stdout = None
|
||||
sys.exit(exc.errno)
|
||||
else:
|
||||
parser.print_usage()
|
||||
return parser
|
||||
|
||||
# When invoked as main program, invoke the profiler on a script
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
777
Lib/pstats.py
vendored
Normal file
777
Lib/pstats.py
vendored
Normal file
@@ -0,0 +1,777 @@
|
||||
"""Class for printing reports on profiled python code."""
|
||||
|
||||
# Written by James Roskind
|
||||
# Based on prior profile module by Sjoerd Mullender...
|
||||
# which was hacked somewhat by: Guido van Rossum
|
||||
|
||||
# Copyright Disney Enterprises, Inc. All Rights Reserved.
|
||||
# Licensed to PSF under a Contributor Agreement
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
# either express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import marshal
|
||||
import re
|
||||
|
||||
from enum import StrEnum, _simple_enum
|
||||
from functools import cmp_to_key
|
||||
from dataclasses import dataclass
|
||||
|
||||
__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"]
|
||||
|
||||
@_simple_enum(StrEnum)
|
||||
class SortKey:
|
||||
CALLS = 'calls', 'ncalls'
|
||||
CUMULATIVE = 'cumulative', 'cumtime'
|
||||
FILENAME = 'filename', 'module'
|
||||
LINE = 'line'
|
||||
NAME = 'name'
|
||||
NFL = 'nfl'
|
||||
PCALLS = 'pcalls'
|
||||
STDNAME = 'stdname'
|
||||
TIME = 'time', 'tottime'
|
||||
|
||||
def __new__(cls, *values):
|
||||
value = values[0]
|
||||
obj = str.__new__(cls, value)
|
||||
obj._value_ = value
|
||||
for other_value in values[1:]:
|
||||
cls._value2member_map_[other_value] = obj
|
||||
obj._all_values = values
|
||||
return obj
|
||||
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
class FunctionProfile:
|
||||
ncalls: str
|
||||
tottime: float
|
||||
percall_tottime: float
|
||||
cumtime: float
|
||||
percall_cumtime: float
|
||||
file_name: str
|
||||
line_number: int
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
class StatsProfile:
|
||||
'''Class for keeping track of an item in inventory.'''
|
||||
total_tt: float
|
||||
func_profiles: dict[str, FunctionProfile]
|
||||
|
||||
class Stats:
|
||||
"""This class is used for creating reports from data generated by the
|
||||
Profile class. It is a "friend" of that class, and imports data either
|
||||
by direct access to members of Profile class, or by reading in a dictionary
|
||||
that was emitted (via marshal) from the Profile class.
|
||||
|
||||
The big change from the previous Profiler (in terms of raw functionality)
|
||||
is that an "add()" method has been provided to combine Stats from
|
||||
several distinct profile runs. Both the constructor and the add()
|
||||
method now take arbitrarily many file names as arguments.
|
||||
|
||||
All the print methods now take an argument that indicates how many lines
|
||||
to print. If the arg is a floating-point number between 0 and 1.0, then
|
||||
it is taken as a decimal percentage of the available lines to be printed
|
||||
(e.g., .1 means print 10% of all available lines). If it is an integer,
|
||||
it is taken to mean the number of lines of data that you wish to have
|
||||
printed.
|
||||
|
||||
The sort_stats() method now processes some additional options (i.e., in
|
||||
addition to the old -1, 0, 1, or 2 that are respectively interpreted as
|
||||
'stdname', 'calls', 'time', and 'cumulative'). It takes either an
|
||||
arbitrary number of quoted strings or SortKey enum to select the sort
|
||||
order.
|
||||
|
||||
For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
|
||||
SortKey.NAME) sorts on the major key of 'internal function time', and on
|
||||
the minor key of 'the name of the function'. Look at the two tables in
|
||||
sort_stats() and get_sort_arg_defs(self) for more examples.
|
||||
|
||||
All methods return self, so you can string together commands like:
|
||||
Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
|
||||
print_stats(5).print_callers(5)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, stream=None):
|
||||
self.stream = stream or sys.stdout
|
||||
if not len(args):
|
||||
arg = None
|
||||
else:
|
||||
arg = args[0]
|
||||
args = args[1:]
|
||||
self.init(arg)
|
||||
self.add(*args)
|
||||
|
||||
def init(self, arg):
|
||||
self.all_callees = None # calc only if needed
|
||||
self.files = []
|
||||
self.fcn_list = None
|
||||
self.total_tt = 0
|
||||
self.total_calls = 0
|
||||
self.prim_calls = 0
|
||||
self.max_name_len = 0
|
||||
self.top_level = set()
|
||||
self.stats = {}
|
||||
self.sort_arg_dict = {}
|
||||
self.load_stats(arg)
|
||||
try:
|
||||
self.get_top_level_stats()
|
||||
except Exception:
|
||||
print("Invalid timing data %s" %
|
||||
(self.files[-1] if self.files else ''), file=self.stream)
|
||||
raise
|
||||
|
||||
def load_stats(self, arg):
|
||||
if arg is None:
|
||||
self.stats = {}
|
||||
return
|
||||
elif isinstance(arg, str):
|
||||
with open(arg, 'rb') as f:
|
||||
self.stats = marshal.load(f)
|
||||
try:
|
||||
file_stats = os.stat(arg)
|
||||
arg = time.ctime(file_stats.st_mtime) + " " + arg
|
||||
except: # in case this is not unix
|
||||
pass
|
||||
self.files = [arg]
|
||||
elif hasattr(arg, 'create_stats'):
|
||||
arg.create_stats()
|
||||
self.stats = arg.stats
|
||||
arg.stats = {}
|
||||
if not self.stats:
|
||||
raise TypeError("Cannot create or construct a %r object from %r"
|
||||
% (self.__class__, arg))
|
||||
return
|
||||
|
||||
def get_top_level_stats(self):
|
||||
for func, (cc, nc, tt, ct, callers) in self.stats.items():
|
||||
self.total_calls += nc
|
||||
self.prim_calls += cc
|
||||
self.total_tt += tt
|
||||
if ("jprofile", 0, "profiler") in callers:
|
||||
self.top_level.add(func)
|
||||
if len(func_std_string(func)) > self.max_name_len:
|
||||
self.max_name_len = len(func_std_string(func))
|
||||
|
||||
def add(self, *arg_list):
|
||||
if not arg_list:
|
||||
return self
|
||||
for item in reversed(arg_list):
|
||||
if type(self) != type(item):
|
||||
item = Stats(item)
|
||||
self.files += item.files
|
||||
self.total_calls += item.total_calls
|
||||
self.prim_calls += item.prim_calls
|
||||
self.total_tt += item.total_tt
|
||||
for func in item.top_level:
|
||||
self.top_level.add(func)
|
||||
|
||||
if self.max_name_len < item.max_name_len:
|
||||
self.max_name_len = item.max_name_len
|
||||
|
||||
self.fcn_list = None
|
||||
|
||||
for func, stat in item.stats.items():
|
||||
if func in self.stats:
|
||||
old_func_stat = self.stats[func]
|
||||
else:
|
||||
old_func_stat = (0, 0, 0, 0, {},)
|
||||
self.stats[func] = add_func_stats(old_func_stat, stat)
|
||||
return self
|
||||
|
||||
def dump_stats(self, filename):
|
||||
"""Write the profile data to a file we know how to load back."""
|
||||
with open(filename, 'wb') as f:
|
||||
marshal.dump(self.stats, f)
|
||||
|
||||
# list the tuple indices and directions for sorting,
|
||||
# along with some printable description
|
||||
sort_arg_dict_default = {
|
||||
"calls" : (((1,-1), ), "call count"),
|
||||
"ncalls" : (((1,-1), ), "call count"),
|
||||
"cumtime" : (((3,-1), ), "cumulative time"),
|
||||
"cumulative": (((3,-1), ), "cumulative time"),
|
||||
"filename" : (((4, 1), ), "file name"),
|
||||
"line" : (((5, 1), ), "line number"),
|
||||
"module" : (((4, 1), ), "file name"),
|
||||
"name" : (((6, 1), ), "function name"),
|
||||
"nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
|
||||
"pcalls" : (((0,-1), ), "primitive call count"),
|
||||
"stdname" : (((7, 1), ), "standard name"),
|
||||
"time" : (((2,-1), ), "internal time"),
|
||||
"tottime" : (((2,-1), ), "internal time"),
|
||||
}
|
||||
|
||||
def get_sort_arg_defs(self):
|
||||
"""Expand all abbreviations that are unique."""
|
||||
if not self.sort_arg_dict:
|
||||
self.sort_arg_dict = dict = {}
|
||||
bad_list = {}
|
||||
for word, tup in self.sort_arg_dict_default.items():
|
||||
fragment = word
|
||||
while fragment:
|
||||
if fragment in dict:
|
||||
bad_list[fragment] = 0
|
||||
break
|
||||
dict[fragment] = tup
|
||||
fragment = fragment[:-1]
|
||||
for word in bad_list:
|
||||
del dict[word]
|
||||
return self.sort_arg_dict
|
||||
|
||||
def sort_stats(self, *field):
|
||||
if not field:
|
||||
self.fcn_list = 0
|
||||
return self
|
||||
if len(field) == 1 and isinstance(field[0], int):
|
||||
# Be compatible with old profiler
|
||||
field = [ {-1: "stdname",
|
||||
0: "calls",
|
||||
1: "time",
|
||||
2: "cumulative"}[field[0]] ]
|
||||
elif len(field) >= 2:
|
||||
for arg in field[1:]:
|
||||
if type(arg) != type(field[0]):
|
||||
raise TypeError("Can't have mixed argument type")
|
||||
|
||||
sort_arg_defs = self.get_sort_arg_defs()
|
||||
|
||||
sort_tuple = ()
|
||||
self.sort_type = ""
|
||||
connector = ""
|
||||
for word in field:
|
||||
if isinstance(word, SortKey):
|
||||
word = word.value
|
||||
sort_tuple = sort_tuple + sort_arg_defs[word][0]
|
||||
self.sort_type += connector + sort_arg_defs[word][1]
|
||||
connector = ", "
|
||||
|
||||
stats_list = []
|
||||
for func, (cc, nc, tt, ct, callers) in self.stats.items():
|
||||
stats_list.append((cc, nc, tt, ct) + func +
|
||||
(func_std_string(func), func))
|
||||
|
||||
stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
|
||||
|
||||
self.fcn_list = fcn_list = []
|
||||
for tuple in stats_list:
|
||||
fcn_list.append(tuple[-1])
|
||||
return self
|
||||
|
||||
def reverse_order(self):
|
||||
if self.fcn_list:
|
||||
self.fcn_list.reverse()
|
||||
return self
|
||||
|
||||
def strip_dirs(self):
|
||||
oldstats = self.stats
|
||||
self.stats = newstats = {}
|
||||
max_name_len = 0
|
||||
for func, (cc, nc, tt, ct, callers) in oldstats.items():
|
||||
newfunc = func_strip_path(func)
|
||||
if len(func_std_string(newfunc)) > max_name_len:
|
||||
max_name_len = len(func_std_string(newfunc))
|
||||
newcallers = {}
|
||||
for func2, caller in callers.items():
|
||||
newcallers[func_strip_path(func2)] = caller
|
||||
|
||||
if newfunc in newstats:
|
||||
newstats[newfunc] = add_func_stats(
|
||||
newstats[newfunc],
|
||||
(cc, nc, tt, ct, newcallers))
|
||||
else:
|
||||
newstats[newfunc] = (cc, nc, tt, ct, newcallers)
|
||||
old_top = self.top_level
|
||||
self.top_level = new_top = set()
|
||||
for func in old_top:
|
||||
new_top.add(func_strip_path(func))
|
||||
|
||||
self.max_name_len = max_name_len
|
||||
|
||||
self.fcn_list = None
|
||||
self.all_callees = None
|
||||
return self
|
||||
|
||||
def calc_callees(self):
|
||||
if self.all_callees:
|
||||
return
|
||||
self.all_callees = all_callees = {}
|
||||
for func, (cc, nc, tt, ct, callers) in self.stats.items():
|
||||
if not func in all_callees:
|
||||
all_callees[func] = {}
|
||||
for func2, caller in callers.items():
|
||||
if not func2 in all_callees:
|
||||
all_callees[func2] = {}
|
||||
all_callees[func2][func] = caller
|
||||
return
|
||||
|
||||
#******************************************************************
|
||||
# The following functions support actual printing of reports
|
||||
#******************************************************************
|
||||
|
||||
# Optional "amount" is either a line count, or a percentage of lines.
|
||||
|
||||
def eval_print_amount(self, sel, list, msg):
|
||||
new_list = list
|
||||
if isinstance(sel, str):
|
||||
try:
|
||||
rex = re.compile(sel)
|
||||
except re.PatternError:
|
||||
msg += " <Invalid regular expression %r>\n" % sel
|
||||
return new_list, msg
|
||||
new_list = []
|
||||
for func in list:
|
||||
if rex.search(func_std_string(func)):
|
||||
new_list.append(func)
|
||||
else:
|
||||
count = len(list)
|
||||
if isinstance(sel, float) and 0.0 <= sel < 1.0:
|
||||
count = int(count * sel + .5)
|
||||
new_list = list[:count]
|
||||
elif isinstance(sel, int) and 0 <= sel < count:
|
||||
count = sel
|
||||
new_list = list[:count]
|
||||
if len(list) != len(new_list):
|
||||
msg += " List reduced from %r to %r due to restriction <%r>\n" % (
|
||||
len(list), len(new_list), sel)
|
||||
|
||||
return new_list, msg
|
||||
|
||||
def get_stats_profile(self):
|
||||
"""This method returns an instance of StatsProfile, which contains a mapping
|
||||
of function names to instances of FunctionProfile. Each FunctionProfile
|
||||
instance holds information related to the function's profile such as how
|
||||
long the function took to run, how many times it was called, etc...
|
||||
"""
|
||||
func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys())
|
||||
if not func_list:
|
||||
return StatsProfile(0, {})
|
||||
|
||||
total_tt = float(f8(self.total_tt))
|
||||
func_profiles = {}
|
||||
stats_profile = StatsProfile(total_tt, func_profiles)
|
||||
|
||||
for func in func_list:
|
||||
cc, nc, tt, ct, callers = self.stats[func]
|
||||
file_name, line_number, func_name = func
|
||||
ncalls = str(nc) if nc == cc else (str(nc) + '/' + str(cc))
|
||||
tottime = float(f8(tt))
|
||||
percall_tottime = -1 if nc == 0 else float(f8(tt/nc))
|
||||
cumtime = float(f8(ct))
|
||||
percall_cumtime = -1 if cc == 0 else float(f8(ct/cc))
|
||||
func_profile = FunctionProfile(
|
||||
ncalls,
|
||||
tottime, # time spent in this function alone
|
||||
percall_tottime,
|
||||
cumtime, # time spent in the function plus all functions that this function called,
|
||||
percall_cumtime,
|
||||
file_name,
|
||||
line_number
|
||||
)
|
||||
func_profiles[func_name] = func_profile
|
||||
|
||||
return stats_profile
|
||||
|
||||
def get_print_list(self, sel_list):
|
||||
width = self.max_name_len
|
||||
if self.fcn_list:
|
||||
stat_list = self.fcn_list[:]
|
||||
msg = " Ordered by: " + self.sort_type + '\n'
|
||||
else:
|
||||
stat_list = list(self.stats.keys())
|
||||
msg = " Random listing order was used\n"
|
||||
|
||||
for selection in sel_list:
|
||||
stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
|
||||
|
||||
count = len(stat_list)
|
||||
|
||||
if not stat_list:
|
||||
return 0, stat_list
|
||||
print(msg, file=self.stream)
|
||||
if count < len(self.stats):
|
||||
width = 0
|
||||
for func in stat_list:
|
||||
if len(func_std_string(func)) > width:
|
||||
width = len(func_std_string(func))
|
||||
return width+2, stat_list
|
||||
|
||||
def print_stats(self, *amount):
|
||||
for filename in self.files:
|
||||
print(filename, file=self.stream)
|
||||
if self.files:
|
||||
print(file=self.stream)
|
||||
indent = ' ' * 8
|
||||
for func in self.top_level:
|
||||
print(indent, func_get_function_name(func), file=self.stream)
|
||||
|
||||
print(indent, self.total_calls, "function calls", end=' ', file=self.stream)
|
||||
if self.total_calls != self.prim_calls:
|
||||
print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream)
|
||||
print("in %.3f seconds" % self.total_tt, file=self.stream)
|
||||
print(file=self.stream)
|
||||
width, list = self.get_print_list(amount)
|
||||
if list:
|
||||
self.print_title()
|
||||
for func in list:
|
||||
self.print_line(func)
|
||||
print(file=self.stream)
|
||||
print(file=self.stream)
|
||||
return self
|
||||
|
||||
def print_callees(self, *amount):
|
||||
width, list = self.get_print_list(amount)
|
||||
if list:
|
||||
self.calc_callees()
|
||||
|
||||
self.print_call_heading(width, "called...")
|
||||
for func in list:
|
||||
if func in self.all_callees:
|
||||
self.print_call_line(width, func, self.all_callees[func])
|
||||
else:
|
||||
self.print_call_line(width, func, {})
|
||||
print(file=self.stream)
|
||||
print(file=self.stream)
|
||||
return self
|
||||
|
||||
def print_callers(self, *amount):
|
||||
width, list = self.get_print_list(amount)
|
||||
if list:
|
||||
self.print_call_heading(width, "was called by...")
|
||||
for func in list:
|
||||
cc, nc, tt, ct, callers = self.stats[func]
|
||||
self.print_call_line(width, func, callers, "<-")
|
||||
print(file=self.stream)
|
||||
print(file=self.stream)
|
||||
return self
|
||||
|
||||
def print_call_heading(self, name_size, column_title):
|
||||
print("Function ".ljust(name_size) + column_title, file=self.stream)
|
||||
# print sub-header only if we have new-style callers
|
||||
subheader = False
|
||||
for cc, nc, tt, ct, callers in self.stats.values():
|
||||
if callers:
|
||||
value = next(iter(callers.values()))
|
||||
subheader = isinstance(value, tuple)
|
||||
break
|
||||
if subheader:
|
||||
print(" "*name_size + " ncalls tottime cumtime", file=self.stream)
|
||||
|
||||
def print_call_line(self, name_size, source, call_dict, arrow="->"):
|
||||
print(func_std_string(source).ljust(name_size) + arrow, end=' ', file=self.stream)
|
||||
if not call_dict:
|
||||
print(file=self.stream)
|
||||
return
|
||||
clist = sorted(call_dict.keys())
|
||||
indent = ""
|
||||
for func in clist:
|
||||
name = func_std_string(func)
|
||||
value = call_dict[func]
|
||||
if isinstance(value, tuple):
|
||||
nc, cc, tt, ct = value
|
||||
if nc != cc:
|
||||
substats = '%d/%d' % (nc, cc)
|
||||
else:
|
||||
substats = '%d' % (nc,)
|
||||
substats = '%s %s %s %s' % (substats.rjust(7+2*len(indent)),
|
||||
f8(tt), f8(ct), name)
|
||||
left_width = name_size + 1
|
||||
else:
|
||||
substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
|
||||
left_width = name_size + 3
|
||||
print(indent*left_width + substats, file=self.stream)
|
||||
indent = " "
|
||||
|
||||
def print_title(self):
|
||||
print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
|
||||
print('filename:lineno(function)', file=self.stream)
|
||||
|
||||
def print_line(self, func): # hack: should print percentages
|
||||
cc, nc, tt, ct, callers = self.stats[func]
|
||||
c = str(nc)
|
||||
if nc != cc:
|
||||
c = c + '/' + str(cc)
|
||||
print(c.rjust(9), end=' ', file=self.stream)
|
||||
print(f8(tt), end=' ', file=self.stream)
|
||||
if nc == 0:
|
||||
print(' '*8, end=' ', file=self.stream)
|
||||
else:
|
||||
print(f8(tt/nc), end=' ', file=self.stream)
|
||||
print(f8(ct), end=' ', file=self.stream)
|
||||
if cc == 0:
|
||||
print(' '*8, end=' ', file=self.stream)
|
||||
else:
|
||||
print(f8(ct/cc), end=' ', file=self.stream)
|
||||
print(func_std_string(func), file=self.stream)
|
||||
|
||||
class TupleComp:
|
||||
"""This class provides a generic function for comparing any two tuples.
|
||||
Each instance records a list of tuple-indices (from most significant
|
||||
to least significant), and sort direction (ascending or descending) for
|
||||
each tuple-index. The compare functions can then be used as the function
|
||||
argument to the system sort() function when a list of tuples need to be
|
||||
sorted in the instances order."""
|
||||
|
||||
def __init__(self, comp_select_list):
|
||||
self.comp_select_list = comp_select_list
|
||||
|
||||
def compare (self, left, right):
|
||||
for index, direction in self.comp_select_list:
|
||||
l = left[index]
|
||||
r = right[index]
|
||||
if l < r:
|
||||
return -direction
|
||||
if l > r:
|
||||
return direction
|
||||
return 0
|
||||
|
||||
|
||||
#**************************************************************************
|
||||
# func_name is a triple (file:string, line:int, name:string)
|
||||
|
||||
def func_strip_path(func_name):
|
||||
filename, line, name = func_name
|
||||
return os.path.basename(filename), line, name
|
||||
|
||||
def func_get_function_name(func):
|
||||
return func[2]
|
||||
|
||||
def func_std_string(func_name): # match what old profile produced
|
||||
if func_name[:2] == ('~', 0):
|
||||
# special case for built-in functions
|
||||
name = func_name[2]
|
||||
if name.startswith('<') and name.endswith('>'):
|
||||
return '{%s}' % name[1:-1]
|
||||
else:
|
||||
return name
|
||||
else:
|
||||
return "%s:%d(%s)" % func_name
|
||||
|
||||
#**************************************************************************
|
||||
# The following functions combine statistics for pairs functions.
|
||||
# The bulk of the processing involves correctly handling "call" lists,
|
||||
# such as callers and callees.
|
||||
#**************************************************************************
|
||||
|
||||
def add_func_stats(target, source):
|
||||
"""Add together all the stats for two profile entries."""
|
||||
cc, nc, tt, ct, callers = source
|
||||
t_cc, t_nc, t_tt, t_ct, t_callers = target
|
||||
return (cc+t_cc, nc+t_nc, tt+t_tt, ct+t_ct,
|
||||
add_callers(t_callers, callers))
|
||||
|
||||
def add_callers(target, source):
|
||||
"""Combine two caller lists in a single list."""
|
||||
new_callers = {}
|
||||
for func, caller in target.items():
|
||||
new_callers[func] = caller
|
||||
for func, caller in source.items():
|
||||
if func in new_callers:
|
||||
if isinstance(caller, tuple):
|
||||
# format used by cProfile
|
||||
new_callers[func] = tuple(i + j for i, j in zip(caller, new_callers[func]))
|
||||
else:
|
||||
# format used by profile
|
||||
new_callers[func] += caller
|
||||
else:
|
||||
new_callers[func] = caller
|
||||
return new_callers
|
||||
|
||||
def count_calls(callers):
|
||||
"""Sum the caller statistics to get total number of calls received."""
|
||||
nc = 0
|
||||
for calls in callers.values():
|
||||
nc += calls
|
||||
return nc
|
||||
|
||||
#**************************************************************************
|
||||
# The following functions support printing of reports
|
||||
#**************************************************************************
|
||||
|
||||
def f8(x):
|
||||
return "%8.3f" % x
|
||||
|
||||
#**************************************************************************
|
||||
# Statistics browser added by ESR, April 2001
|
||||
#**************************************************************************
|
||||
|
||||
if __name__ == '__main__':
|
||||
import cmd
|
||||
try:
|
||||
import readline # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class ProfileBrowser(cmd.Cmd):
|
||||
def __init__(self, profile=None):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.prompt = "% "
|
||||
self.stats = None
|
||||
self.stream = sys.stdout
|
||||
if profile is not None:
|
||||
self.do_read(profile)
|
||||
|
||||
def generic(self, fn, line):
|
||||
args = line.split()
|
||||
processed = []
|
||||
for term in args:
|
||||
try:
|
||||
processed.append(int(term))
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
frac = float(term)
|
||||
if frac > 1 or frac < 0:
|
||||
print("Fraction argument must be in [0, 1]", file=self.stream)
|
||||
continue
|
||||
processed.append(frac)
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
processed.append(term)
|
||||
if self.stats:
|
||||
getattr(self.stats, fn)(*processed)
|
||||
else:
|
||||
print("No statistics object is loaded.", file=self.stream)
|
||||
return 0
|
||||
def generic_help(self):
|
||||
print("Arguments may be:", file=self.stream)
|
||||
print("* An integer maximum number of entries to print.", file=self.stream)
|
||||
print("* A decimal fractional number between 0 and 1, controlling", file=self.stream)
|
||||
print(" what fraction of selected entries to print.", file=self.stream)
|
||||
print("* A regular expression; only entries with function names", file=self.stream)
|
||||
print(" that match it are printed.", file=self.stream)
|
||||
|
||||
def do_add(self, line):
|
||||
if self.stats:
|
||||
try:
|
||||
self.stats.add(line)
|
||||
except OSError as e:
|
||||
print("Failed to load statistics for %s: %s" % (line, e), file=self.stream)
|
||||
else:
|
||||
print("No statistics object is loaded.", file=self.stream)
|
||||
return 0
|
||||
def help_add(self):
|
||||
print("Add profile info from given file to current statistics object.", file=self.stream)
|
||||
|
||||
def do_callees(self, line):
|
||||
return self.generic('print_callees', line)
|
||||
def help_callees(self):
|
||||
print("Print callees statistics from the current stat object.", file=self.stream)
|
||||
self.generic_help()
|
||||
|
||||
def do_callers(self, line):
|
||||
return self.generic('print_callers', line)
|
||||
def help_callers(self):
|
||||
print("Print callers statistics from the current stat object.", file=self.stream)
|
||||
self.generic_help()
|
||||
|
||||
def do_EOF(self, line):
|
||||
print("", file=self.stream)
|
||||
return 1
|
||||
def help_EOF(self):
|
||||
print("Leave the profile browser.", file=self.stream)
|
||||
|
||||
def do_quit(self, line):
|
||||
return 1
|
||||
def help_quit(self):
|
||||
print("Leave the profile browser.", file=self.stream)
|
||||
|
||||
def do_read(self, line):
|
||||
if line:
|
||||
try:
|
||||
self.stats = Stats(line)
|
||||
except OSError as err:
|
||||
print(err.args[1], file=self.stream)
|
||||
return
|
||||
except Exception as err:
|
||||
print(err.__class__.__name__ + ':', err, file=self.stream)
|
||||
return
|
||||
self.prompt = line + "% "
|
||||
elif len(self.prompt) > 2:
|
||||
line = self.prompt[:-2]
|
||||
self.do_read(line)
|
||||
else:
|
||||
print("No statistics object is current -- cannot reload.", file=self.stream)
|
||||
return 0
|
||||
def help_read(self):
|
||||
print("Read in profile data from a specified file.", file=self.stream)
|
||||
print("Without argument, reload the current file.", file=self.stream)
|
||||
|
||||
def do_reverse(self, line):
|
||||
if self.stats:
|
||||
self.stats.reverse_order()
|
||||
else:
|
||||
print("No statistics object is loaded.", file=self.stream)
|
||||
return 0
|
||||
def help_reverse(self):
|
||||
print("Reverse the sort order of the profiling report.", file=self.stream)
|
||||
|
||||
def do_sort(self, line):
|
||||
if not self.stats:
|
||||
print("No statistics object is loaded.", file=self.stream)
|
||||
return
|
||||
abbrevs = self.stats.get_sort_arg_defs()
|
||||
if line and all((x in abbrevs) for x in line.split()):
|
||||
self.stats.sort_stats(*line.split())
|
||||
else:
|
||||
print("Valid sort keys (unique prefixes are accepted):", file=self.stream)
|
||||
for (key, value) in Stats.sort_arg_dict_default.items():
|
||||
print("%s -- %s" % (key, value[1]), file=self.stream)
|
||||
return 0
|
||||
def help_sort(self):
|
||||
print("Sort profile data according to specified keys.", file=self.stream)
|
||||
print("(Typing `sort' without arguments lists valid keys.)", file=self.stream)
|
||||
def complete_sort(self, text, *args):
|
||||
return [a for a in Stats.sort_arg_dict_default if a.startswith(text)]
|
||||
|
||||
def do_stats(self, line):
|
||||
return self.generic('print_stats', line)
|
||||
def help_stats(self):
|
||||
print("Print statistics from the current stat object.", file=self.stream)
|
||||
self.generic_help()
|
||||
|
||||
def do_strip(self, line):
|
||||
if self.stats:
|
||||
self.stats.strip_dirs()
|
||||
else:
|
||||
print("No statistics object is loaded.", file=self.stream)
|
||||
def help_strip(self):
|
||||
print("Strip leading path information from filenames in the report.", file=self.stream)
|
||||
|
||||
def help_help(self):
|
||||
print("Show help for a given command.", file=self.stream)
|
||||
|
||||
def postcmd(self, stop, line):
|
||||
if stop:
|
||||
return stop
|
||||
return None
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
initprofile = sys.argv[1]
|
||||
else:
|
||||
initprofile = None
|
||||
try:
|
||||
browser = ProfileBrowser(initprofile)
|
||||
for profile in sys.argv[2:]:
|
||||
browser.do_add(profile)
|
||||
print("Welcome to the profile statistics browser.", file=browser.stream)
|
||||
browser.cmdloop()
|
||||
print("Goodbye.", file=browser.stream)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
# That's all, folks.
|
||||
47
Lib/pty.py
vendored
47
Lib/pty.py
vendored
@@ -32,27 +32,18 @@ def openpty():
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
master_fd, slave_name = _open_terminal()
|
||||
slave_fd = slave_open(slave_name)
|
||||
return master_fd, slave_fd
|
||||
|
||||
def master_open():
|
||||
"""master_open() -> (master_fd, slave_name)
|
||||
Open a pty master and return the fd, and the filename of the slave end.
|
||||
Deprecated, use openpty() instead."""
|
||||
|
||||
import warnings
|
||||
warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14
|
||||
|
||||
slave_fd = os.open(slave_name, os.O_RDWR)
|
||||
try:
|
||||
master_fd, slave_fd = os.openpty()
|
||||
except (AttributeError, OSError):
|
||||
from fcntl import ioctl, I_PUSH
|
||||
except ImportError:
|
||||
return master_fd, slave_fd
|
||||
try:
|
||||
ioctl(slave_fd, I_PUSH, "ptem")
|
||||
ioctl(slave_fd, I_PUSH, "ldterm")
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
slave_name = os.ttyname(slave_fd)
|
||||
os.close(slave_fd)
|
||||
return master_fd, slave_name
|
||||
|
||||
return _open_terminal()
|
||||
return master_fd, slave_fd
|
||||
|
||||
def _open_terminal():
|
||||
"""Open pty master and return (master_fd, tty_name)."""
|
||||
@@ -66,26 +57,6 @@ def _open_terminal():
|
||||
return (fd, '/dev/tty' + x + y)
|
||||
raise OSError('out of pty devices')
|
||||
|
||||
def slave_open(tty_name):
|
||||
"""slave_open(tty_name) -> slave_fd
|
||||
Open the pty slave and acquire the controlling terminal, returning
|
||||
opened filedescriptor.
|
||||
Deprecated, use openpty() instead."""
|
||||
|
||||
import warnings
|
||||
warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14
|
||||
|
||||
result = os.open(tty_name, os.O_RDWR)
|
||||
try:
|
||||
from fcntl import ioctl, I_PUSH
|
||||
except ImportError:
|
||||
return result
|
||||
try:
|
||||
ioctl(result, I_PUSH, "ptem")
|
||||
ioctl(result, I_PUSH, "ldterm")
|
||||
except OSError:
|
||||
pass
|
||||
return result
|
||||
|
||||
def fork():
|
||||
"""fork() -> (pid, master_fd)
|
||||
|
||||
2
Lib/pydoc_data/module_docs.py
vendored
2
Lib/pydoc_data/module_docs.py
vendored
@@ -1,4 +1,4 @@
|
||||
# Autogenerated by Sphinx on Tue Feb 3 17:32:13 2026
|
||||
# Autogenerated by Sphinx on Sun May 10 13:21:26 2026
|
||||
# as part of the release process.
|
||||
|
||||
module_docs = {
|
||||
|
||||
909
Lib/pydoc_data/topics.py
vendored
909
Lib/pydoc_data/topics.py
vendored
File diff suppressed because it is too large
Load Diff
17
Lib/random.py
vendored
17
Lib/random.py
vendored
@@ -836,7 +836,11 @@ class Random(_random.Random):
|
||||
if not c:
|
||||
return x
|
||||
while True:
|
||||
y += _floor(_log2(random()) / c) + 1
|
||||
try:
|
||||
y += _floor(_log2(random()) / c) + 1
|
||||
except ValueError:
|
||||
# Reject case where random() returned 0.0
|
||||
continue
|
||||
if y > n:
|
||||
return x
|
||||
x += 1
|
||||
@@ -844,8 +848,8 @@ class Random(_random.Random):
|
||||
# BTRS: Transformed rejection with squeeze method by Wolfgang Hörmann
|
||||
# https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.47.8407&rep=rep1&type=pdf
|
||||
assert n*p >= 10.0 and p <= 0.5
|
||||
setup_complete = False
|
||||
|
||||
setup_complete = False
|
||||
spq = _sqrt(n * p * (1.0 - p)) # Standard deviation of the distribution
|
||||
b = 1.15 + 2.53 * spq
|
||||
a = -0.0873 + 0.0248 * b + 0.01 * p
|
||||
@@ -860,22 +864,23 @@ class Random(_random.Random):
|
||||
k = _floor((2.0 * a / us + b) * u + c)
|
||||
if k < 0 or k > n:
|
||||
continue
|
||||
v = random()
|
||||
|
||||
# The early-out "squeeze" test substantially reduces
|
||||
# the number of acceptance condition evaluations.
|
||||
v = random()
|
||||
if us >= 0.07 and v <= vr:
|
||||
return k
|
||||
|
||||
# Acceptance-rejection test.
|
||||
# Note, the original paper erroneously omits the call to log(v)
|
||||
# when comparing to the log of the rescaled binomial distribution.
|
||||
if not setup_complete:
|
||||
alpha = (2.83 + 5.1 / b) * spq
|
||||
lpq = _log(p / (1.0 - p))
|
||||
m = _floor((n + 1) * p) # Mode of the distribution
|
||||
h = _lgamma(m + 1) + _lgamma(n - m + 1)
|
||||
setup_complete = True # Only needs to be done once
|
||||
|
||||
# Acceptance-rejection test.
|
||||
# Note, the original paper erroneously omits the call to log(v)
|
||||
# when comparing to the log of the rescaled binomial distribution.
|
||||
v *= alpha / (a / (us * us) + b)
|
||||
if _log(v) <= h - _lgamma(k + 1) - _lgamma(n - k + 1) + (k - m) * lpq:
|
||||
return k
|
||||
|
||||
25
Lib/runpy.py
vendored
25
Lib/runpy.py
vendored
@@ -103,8 +103,10 @@ def _run_module_code(code, init_globals=None,
|
||||
|
||||
# Helper to get the full name, spec and code for a module
|
||||
def _get_module_details(mod_name, error=ImportError):
|
||||
# name= is only accepted by ImportError and its subclasses.
|
||||
kwargs = {"name": mod_name} if issubclass(error, ImportError) else {}
|
||||
if mod_name.startswith("."):
|
||||
raise error("Relative module names not supported")
|
||||
raise error("Relative module names not supported", **kwargs)
|
||||
pkg_name, _, _ = mod_name.rpartition(".")
|
||||
if pkg_name:
|
||||
# Try importing the parent to avoid catching initialization errors
|
||||
@@ -137,12 +139,13 @@ def _get_module_details(mod_name, error=ImportError):
|
||||
if mod_name.endswith(".py"):
|
||||
msg += (f". Try using '{mod_name[:-3]}' instead of "
|
||||
f"'{mod_name}' as the module name.")
|
||||
raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
|
||||
raise error(msg.format(mod_name, type(ex).__name__, ex),
|
||||
**kwargs) from ex
|
||||
if spec is None:
|
||||
raise error("No module named %s" % mod_name)
|
||||
raise error("No module named %s" % mod_name, **kwargs)
|
||||
if spec.submodule_search_locations is not None:
|
||||
if mod_name == "__main__" or mod_name.endswith(".__main__"):
|
||||
raise error("Cannot use package as __main__ module")
|
||||
raise error("Cannot use package as __main__ module", **kwargs)
|
||||
try:
|
||||
pkg_main_name = mod_name + ".__main__"
|
||||
return _get_module_details(pkg_main_name, error)
|
||||
@@ -150,17 +153,19 @@ def _get_module_details(mod_name, error=ImportError):
|
||||
if mod_name not in sys.modules:
|
||||
raise # No module loaded; being a package is irrelevant
|
||||
raise error(("%s; %r is a package and cannot " +
|
||||
"be directly executed") %(e, mod_name))
|
||||
"be directly executed") %(e, mod_name),
|
||||
**kwargs)
|
||||
loader = spec.loader
|
||||
if loader is None:
|
||||
raise error("%r is a namespace package and cannot be executed"
|
||||
% mod_name)
|
||||
% mod_name,
|
||||
**kwargs)
|
||||
try:
|
||||
code = loader.get_code(mod_name)
|
||||
except ImportError as e:
|
||||
raise error(format(e)) from e
|
||||
raise error(format(e), **kwargs) from e
|
||||
if code is None:
|
||||
raise error("No code object available for %s" % mod_name)
|
||||
raise error("No code object available for %s" % mod_name, **kwargs)
|
||||
return mod_name, spec, code
|
||||
|
||||
class _Error(Exception):
|
||||
@@ -234,6 +239,7 @@ def _get_main_module_details(error=ImportError):
|
||||
# Also moves the standard __main__ out of the way so that the
|
||||
# preexisting __loader__ entry doesn't cause issues
|
||||
main_name = "__main__"
|
||||
kwargs = {"name": main_name} if issubclass(error, ImportError) else {}
|
||||
saved_main = sys.modules[main_name]
|
||||
del sys.modules[main_name]
|
||||
try:
|
||||
@@ -241,7 +247,8 @@ def _get_main_module_details(error=ImportError):
|
||||
except ImportError as exc:
|
||||
if main_name in str(exc):
|
||||
raise error("can't find %r module in %r" %
|
||||
(main_name, sys.path[0])) from exc
|
||||
(main_name, sys.path[0]),
|
||||
**kwargs) from exc
|
||||
raise
|
||||
finally:
|
||||
sys.modules[main_name] = saved_main
|
||||
|
||||
24
Lib/shutil.py
vendored
24
Lib/shutil.py
vendored
@@ -1314,27 +1314,9 @@ def _unpack_zipfile(filename, extract_dir):
|
||||
if not zipfile.is_zipfile(filename):
|
||||
raise ReadError("%s is not a zip file" % filename)
|
||||
|
||||
zip = zipfile.ZipFile(filename)
|
||||
try:
|
||||
for info in zip.infolist():
|
||||
name = info.filename
|
||||
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if name.startswith('/') or '..' in name:
|
||||
continue
|
||||
|
||||
targetpath = os.path.join(extract_dir, *name.split('/'))
|
||||
if not targetpath:
|
||||
continue
|
||||
|
||||
_ensure_directory(targetpath)
|
||||
if not name.endswith('/'):
|
||||
# file
|
||||
with zip.open(name, 'r') as source, \
|
||||
open(targetpath, 'wb') as target:
|
||||
copyfileobj(source, target)
|
||||
finally:
|
||||
zip.close()
|
||||
with zipfile.ZipFile(filename) as zip:
|
||||
zip._ignore_invalid_names = True
|
||||
zip.extractall(extract_dir)
|
||||
|
||||
def _unpack_tarfile(filename, extract_dir, *, filter=None):
|
||||
"""Unpack tar/tar.gz/tar.bz2/tar.xz/tar.zst `filename` to `extract_dir`
|
||||
|
||||
2
Lib/site.py
vendored
2
Lib/site.py
vendored
@@ -290,7 +290,7 @@ def check_enableusersite():
|
||||
|
||||
# Copy of sysconfig._get_implementation()
|
||||
def _get_implementation():
|
||||
return 'RustPython' # XXX: RustPython; for site-packages
|
||||
return 'RustPython' # XXX: RUSTPYTHON; for site-packages
|
||||
|
||||
# Copy of sysconfig._getuserbase()
|
||||
def _getuserbase():
|
||||
|
||||
2
Lib/smtplib.py
vendored
2
Lib/smtplib.py
vendored
@@ -251,7 +251,6 @@ class SMTP:
|
||||
will be used.
|
||||
|
||||
"""
|
||||
self._host = host
|
||||
self.timeout = timeout
|
||||
self.esmtp_features = {}
|
||||
self.command_encoding = 'ascii'
|
||||
@@ -342,6 +341,7 @@ class SMTP:
|
||||
port = int(port)
|
||||
except ValueError:
|
||||
raise OSError("nonnumeric port")
|
||||
self._host = host
|
||||
if not port:
|
||||
port = self.default_port
|
||||
sys.audit("smtplib.connect", self, host, port)
|
||||
|
||||
28
Lib/socket.py
vendored
28
Lib/socket.py
vendored
@@ -640,18 +640,22 @@ def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
|
||||
# Authenticating avoids using a connection from something else
|
||||
# able to connect to {host}:{port} instead of us.
|
||||
# We expect only AF_INET and AF_INET6 families.
|
||||
try:
|
||||
if (
|
||||
ssock.getsockname() != csock.getpeername()
|
||||
or csock.getsockname() != ssock.getpeername()
|
||||
):
|
||||
raise ConnectionError("Unexpected peer connection")
|
||||
except:
|
||||
# getsockname() and getpeername() can fail
|
||||
# if either socket isn't connected.
|
||||
ssock.close()
|
||||
csock.close()
|
||||
raise
|
||||
#
|
||||
# Note that we skip this on WASI because on that platorm the client socket
|
||||
# may not have finished connecting by the time we've reached this point (gh-146139).
|
||||
if sys.platform != "wasi":
|
||||
try:
|
||||
if (
|
||||
ssock.getsockname() != csock.getpeername()
|
||||
or csock.getsockname() != ssock.getpeername()
|
||||
):
|
||||
raise ConnectionError("Unexpected peer connection")
|
||||
except:
|
||||
# getsockname() and getpeername() can fail
|
||||
# if either socket isn't connected.
|
||||
ssock.close()
|
||||
csock.close()
|
||||
raise
|
||||
|
||||
return (ssock, csock)
|
||||
|
||||
|
||||
19
Lib/subprocess.py
vendored
19
Lib/subprocess.py
vendored
@@ -351,15 +351,16 @@ def _args_from_interpreter_flags():
|
||||
# -X options
|
||||
if dev_mode:
|
||||
args.extend(('-X', 'dev'))
|
||||
for opt in ('faulthandler', 'tracemalloc', 'importtime',
|
||||
'frozen_modules', 'showrefcount', 'utf8', 'gil'):
|
||||
if opt in xoptions:
|
||||
value = xoptions[opt]
|
||||
if value is True:
|
||||
arg = opt
|
||||
else:
|
||||
arg = '%s=%s' % (opt, value)
|
||||
args.extend(('-X', arg))
|
||||
for opt in sorted(xoptions):
|
||||
if opt == 'dev':
|
||||
# handled above via sys.flags.dev_mode
|
||||
continue
|
||||
value = xoptions[opt]
|
||||
if value is True:
|
||||
arg = opt
|
||||
else:
|
||||
arg = '%s=%s' % (opt, value)
|
||||
args.extend(('-X', arg))
|
||||
|
||||
return args
|
||||
|
||||
|
||||
14
Lib/sysconfig/__init__.py
vendored
14
Lib/sysconfig/__init__.py
vendored
@@ -107,7 +107,7 @@ else:
|
||||
_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']
|
||||
|
||||
def _get_implementation():
|
||||
return 'RustPython' # XXX: For site-packages
|
||||
return 'RustPython' # XXX: RUSTPYTHON; For site-packages
|
||||
|
||||
# NOTE: site.py has copy of this function.
|
||||
# Sync it when modify this function.
|
||||
@@ -698,11 +698,19 @@ def get_platform():
|
||||
release = get_config_var("ANDROID_API_LEVEL")
|
||||
|
||||
# Wheel tags use the ABI names from Android's own tools.
|
||||
# When Python is running on 32-bit ARM Android on a 64-bit ARM kernel,
|
||||
# 'os.uname().machine' is 'armv8l'. Such devices run the same userspace
|
||||
# code as 'armv7l' devices.
|
||||
# During the build process of the Android testbed when targeting 32-bit ARM,
|
||||
# '_PYTHON_HOST_PLATFORM' is 'arm-linux-androideabi', so 'machine' becomes
|
||||
# 'arm'.
|
||||
machine = {
|
||||
"x86_64": "x86_64",
|
||||
"i686": "x86",
|
||||
"aarch64": "arm64_v8a",
|
||||
"arm": "armeabi_v7a",
|
||||
"armv7l": "armeabi_v7a",
|
||||
"armv8l": "armeabi_v7a",
|
||||
"i686": "x86",
|
||||
"x86_64": "x86_64",
|
||||
}[machine]
|
||||
elif osname == "linux":
|
||||
# At least on Linux/Intel, 'machine' is the processor --
|
||||
|
||||
29
Lib/tarfile.py
vendored
29
Lib/tarfile.py
vendored
@@ -1278,6 +1278,20 @@ class TarInfo(object):
|
||||
@classmethod
|
||||
def frombuf(cls, buf, encoding, errors):
|
||||
"""Construct a TarInfo object from a 512 byte bytes object.
|
||||
|
||||
To support the old v7 tar format AREGTYPE headers are
|
||||
transformed to DIRTYPE headers if their name ends in '/'.
|
||||
"""
|
||||
return cls._frombuf(buf, encoding, errors)
|
||||
|
||||
@classmethod
|
||||
def _frombuf(cls, buf, encoding, errors, *, dircheck=True):
|
||||
"""Construct a TarInfo object from a 512 byte bytes object.
|
||||
|
||||
If ``dircheck`` is set to ``True`` then ``AREGTYPE`` headers will
|
||||
be normalized to ``DIRTYPE`` if the name ends in a trailing slash.
|
||||
``dircheck`` must be set to ``False`` if this function is called
|
||||
on a follow-up header such as ``GNUTYPE_LONGNAME``.
|
||||
"""
|
||||
if len(buf) == 0:
|
||||
raise EmptyHeaderError("empty header")
|
||||
@@ -1308,7 +1322,7 @@ class TarInfo(object):
|
||||
|
||||
# Old V7 tar format represents a directory as a regular
|
||||
# file with a trailing slash.
|
||||
if obj.type == AREGTYPE and obj.name.endswith("/"):
|
||||
if dircheck and obj.type == AREGTYPE and obj.name.endswith("/"):
|
||||
obj.type = DIRTYPE
|
||||
|
||||
# The old GNU sparse format occupies some of the unused
|
||||
@@ -1343,8 +1357,15 @@ class TarInfo(object):
|
||||
"""Return the next TarInfo object from TarFile object
|
||||
tarfile.
|
||||
"""
|
||||
return cls._fromtarfile(tarfile)
|
||||
|
||||
@classmethod
|
||||
def _fromtarfile(cls, tarfile, *, dircheck=True):
|
||||
"""
|
||||
See dircheck documentation in _frombuf().
|
||||
"""
|
||||
buf = tarfile.fileobj.read(BLOCKSIZE)
|
||||
obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
|
||||
obj = cls._frombuf(buf, tarfile.encoding, tarfile.errors, dircheck=dircheck)
|
||||
obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
|
||||
return obj._proc_member(tarfile)
|
||||
|
||||
@@ -1402,7 +1423,7 @@ class TarInfo(object):
|
||||
|
||||
# Fetch the next header and process it.
|
||||
try:
|
||||
next = self.fromtarfile(tarfile)
|
||||
next = self._fromtarfile(tarfile, dircheck=False)
|
||||
except HeaderError as e:
|
||||
raise SubsequentHeaderError(str(e)) from None
|
||||
|
||||
@@ -1537,7 +1558,7 @@ class TarInfo(object):
|
||||
|
||||
# Fetch the next header.
|
||||
try:
|
||||
next = self.fromtarfile(tarfile)
|
||||
next = self._fromtarfile(tarfile, dircheck=False)
|
||||
except HeaderError as e:
|
||||
raise SubsequentHeaderError(str(e)) from None
|
||||
|
||||
|
||||
36
Lib/tempfile.py
vendored
36
Lib/tempfile.py
vendored
@@ -57,10 +57,11 @@ _bin_openflags = _text_openflags
|
||||
if hasattr(_os, 'O_BINARY'):
|
||||
_bin_openflags |= _os.O_BINARY
|
||||
|
||||
if hasattr(_os, 'TMP_MAX'):
|
||||
TMP_MAX = _os.TMP_MAX
|
||||
else:
|
||||
TMP_MAX = 10000
|
||||
# This is more than enough.
|
||||
# Each name contains over 40 random bits. Even with a million temporary
|
||||
# files, the chance of a conflict is less than 1 in a million, and with
|
||||
# 20 attempts, it is less than 1e-120.
|
||||
TMP_MAX = 20
|
||||
|
||||
# This variable _was_ unused for legacy reasons, see issue 10354.
|
||||
# But as of 3.5 we actually use it at runtime so changing it would
|
||||
@@ -196,8 +197,7 @@ def _get_default_tempdir(dirlist=None):
|
||||
for dir in dirlist:
|
||||
if dir != _os.curdir:
|
||||
dir = _os.path.abspath(dir)
|
||||
# Try only a few names per directory.
|
||||
for seq in range(100):
|
||||
for seq in range(TMP_MAX):
|
||||
name = next(namer)
|
||||
filename = _os.path.join(dir, name)
|
||||
try:
|
||||
@@ -213,10 +213,8 @@ def _get_default_tempdir(dirlist=None):
|
||||
except FileExistsError:
|
||||
pass
|
||||
except PermissionError:
|
||||
# This exception is thrown when a directory with the chosen name
|
||||
# already exists on windows.
|
||||
if (_os.name == 'nt' and _os.path.isdir(dir) and
|
||||
_os.access(dir, _os.W_OK)):
|
||||
# See the comment in mkdtemp().
|
||||
if _os.name == 'nt' and _os.path.isdir(dir):
|
||||
continue
|
||||
break # no point trying more names in this directory
|
||||
except OSError:
|
||||
@@ -258,10 +256,8 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type):
|
||||
except FileExistsError:
|
||||
continue # try again
|
||||
except PermissionError:
|
||||
# This exception is thrown when a directory with the chosen name
|
||||
# already exists on windows.
|
||||
if (_os.name == 'nt' and _os.path.isdir(dir) and
|
||||
_os.access(dir, _os.W_OK)):
|
||||
# See the comment in mkdtemp().
|
||||
if _os.name == 'nt' and _os.path.isdir(dir) and seq < TMP_MAX - 1:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
@@ -386,10 +382,14 @@ def mkdtemp(suffix=None, prefix=None, dir=None):
|
||||
except FileExistsError:
|
||||
continue # try again
|
||||
except PermissionError:
|
||||
# This exception is thrown when a directory with the chosen name
|
||||
# already exists on windows.
|
||||
if (_os.name == 'nt' and _os.path.isdir(dir) and
|
||||
_os.access(dir, _os.W_OK)):
|
||||
# On Posix, this exception is raised when the user has no
|
||||
# write access to the parent directory.
|
||||
# On Windows, it is also raised when a directory with
|
||||
# the chosen name already exists, or if the parent directory
|
||||
# is not a directory.
|
||||
# We cannot distinguish between "directory-exists-error" and
|
||||
# "access-denied-error".
|
||||
if _os.name == 'nt' and _os.path.isdir(dir) and seq < TMP_MAX - 1:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
17035
Lib/test/NormalizationTest-3.2.0.txt
vendored
Normal file
17035
Lib/test/NormalizationTest-3.2.0.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
Lib/test/_test_eintr.py
vendored
1
Lib/test/_test_eintr.py
vendored
@@ -163,7 +163,6 @@ class OSEINTRTest(EINTRBaseTest):
|
||||
self.assertEqual(os.readinto(fd, buffer), len(expected))
|
||||
self.assertEqual(buffer, expected)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; InterruptedError: [Errno 4] Interrupted system call
|
||||
def test_write(self):
|
||||
rd, wr = os.pipe()
|
||||
self.addCleanup(os.close, wr)
|
||||
|
||||
62
Lib/test/_test_embed_structseq.py
vendored
Normal file
62
Lib/test/_test_embed_structseq.py
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
|
||||
|
||||
# bpo-46417: Test that structseq types used by the sys module are still
|
||||
# valid when Py_Finalize()/Py_Initialize() are called multiple times.
|
||||
class TestStructSeq(unittest.TestCase):
|
||||
# test PyTypeObject members
|
||||
def check_structseq(self, obj_type):
|
||||
# ob_refcnt
|
||||
self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
|
||||
# tp_base
|
||||
self.assertIsSubclass(obj_type, tuple)
|
||||
# tp_bases
|
||||
self.assertEqual(obj_type.__bases__, (tuple,))
|
||||
# tp_dict
|
||||
self.assertIsInstance(obj_type.__dict__, types.MappingProxyType)
|
||||
# tp_mro
|
||||
self.assertEqual(obj_type.__mro__, (obj_type, tuple, object))
|
||||
# tp_name
|
||||
self.assertIsInstance(type.__name__, str)
|
||||
# tp_subclasses
|
||||
self.assertEqual(obj_type.__subclasses__(), [])
|
||||
|
||||
def test_sys_attrs(self):
|
||||
for attr_name in (
|
||||
'flags', # FlagsType
|
||||
'float_info', # FloatInfoType
|
||||
'hash_info', # Hash_InfoType
|
||||
'int_info', # Int_InfoType
|
||||
'thread_info', # ThreadInfoType
|
||||
'version_info', # VersionInfoType
|
||||
):
|
||||
with self.subTest(attr=attr_name):
|
||||
attr = getattr(sys, attr_name)
|
||||
self.check_structseq(type(attr))
|
||||
|
||||
def test_sys_funcs(self):
|
||||
func_names = ['get_asyncgen_hooks'] # AsyncGenHooksType
|
||||
if hasattr(sys, 'getwindowsversion'):
|
||||
func_names.append('getwindowsversion') # WindowsVersionType
|
||||
for func_name in func_names:
|
||||
with self.subTest(func=func_name):
|
||||
func = getattr(sys, func_name)
|
||||
obj = func()
|
||||
self.check_structseq(type(obj))
|
||||
|
||||
|
||||
try:
|
||||
unittest.main(
|
||||
module=(
|
||||
'__main__'
|
||||
if __name__ == '__main__'
|
||||
# Avoiding a circular import:
|
||||
else sys.modules['test._test_embed_structseq']
|
||||
)
|
||||
)
|
||||
except SystemExit as exc:
|
||||
if exc.args[0] != 0:
|
||||
raise
|
||||
print("Tests passed")
|
||||
4
Lib/test/_test_multiprocessing.py
vendored
4
Lib/test/_test_multiprocessing.py
vendored
@@ -4813,9 +4813,9 @@ class _TestFinalize(BaseTestCase):
|
||||
result = [obj for obj in iter(conn.recv, 'STOP')]
|
||||
self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e'])
|
||||
|
||||
# TODO: RUSTPYTHON; SIGSEGV due to dict thread-safety issue under aggressive GC
|
||||
@unittest.skip("TODO: RUSTPYTHON")
|
||||
@support.requires_resource('cpu')
|
||||
# TODO: RUSTPYTHON; dict iteration races with concurrent GC mutations
|
||||
@unittest.expectedFailure
|
||||
def test_thread_safety(self):
|
||||
# bpo-24484: _run_finalizers() should be thread-safe
|
||||
def cb():
|
||||
|
||||
681
Lib/test/audit-tests.py
vendored
Normal file
681
Lib/test/audit-tests.py
vendored
Normal file
@@ -0,0 +1,681 @@
|
||||
"""This script contains the actual auditing tests.
|
||||
|
||||
It should not be imported directly, but should be run by the test_audit
|
||||
module with arguments identifying each test.
|
||||
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
class TestHook:
|
||||
"""Used in standard hook tests to collect any logged events.
|
||||
|
||||
Should be used in a with block to ensure that it has no impact
|
||||
after the test completes.
|
||||
"""
|
||||
|
||||
def __init__(self, raise_on_events=None, exc_type=RuntimeError):
|
||||
self.raise_on_events = raise_on_events or ()
|
||||
self.exc_type = exc_type
|
||||
self.seen = []
|
||||
self.closed = False
|
||||
|
||||
def __enter__(self, *a):
|
||||
sys.addaudithook(self)
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
@property
|
||||
def seen_events(self):
|
||||
return [i[0] for i in self.seen]
|
||||
|
||||
def __call__(self, event, args):
|
||||
if self.closed:
|
||||
return
|
||||
self.seen.append((event, args))
|
||||
if event in self.raise_on_events:
|
||||
raise self.exc_type("saw event " + event)
|
||||
|
||||
|
||||
# Simple helpers, since we are not in unittest here
|
||||
def assertEqual(x, y):
|
||||
if x != y:
|
||||
raise AssertionError(f"{x!r} should equal {y!r}")
|
||||
|
||||
|
||||
def assertIn(el, series):
|
||||
if el not in series:
|
||||
raise AssertionError(f"{el!r} should be in {series!r}")
|
||||
|
||||
|
||||
def assertNotIn(el, series):
|
||||
if el in series:
|
||||
raise AssertionError(f"{el!r} should not be in {series!r}")
|
||||
|
||||
|
||||
def assertSequenceEqual(x, y):
|
||||
if len(x) != len(y):
|
||||
raise AssertionError(f"{x!r} should equal {y!r}")
|
||||
if any(ix != iy for ix, iy in zip(x, y)):
|
||||
raise AssertionError(f"{x!r} should equal {y!r}")
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def assertRaises(ex_type):
|
||||
try:
|
||||
yield
|
||||
assert False, f"expected {ex_type}"
|
||||
except BaseException as ex:
|
||||
if isinstance(ex, AssertionError):
|
||||
raise
|
||||
assert type(ex) is ex_type, f"{ex} should be {ex_type}"
|
||||
|
||||
|
||||
def test_basic():
|
||||
with TestHook() as hook:
|
||||
sys.audit("test_event", 1, 2, 3)
|
||||
assertEqual(hook.seen[0][0], "test_event")
|
||||
assertEqual(hook.seen[0][1], (1, 2, 3))
|
||||
|
||||
|
||||
def test_block_add_hook():
|
||||
# Raising an exception should prevent a new hook from being added,
|
||||
# but will not propagate out.
|
||||
with TestHook(raise_on_events="sys.addaudithook") as hook1:
|
||||
with TestHook() as hook2:
|
||||
sys.audit("test_event")
|
||||
assertIn("test_event", hook1.seen_events)
|
||||
assertNotIn("test_event", hook2.seen_events)
|
||||
|
||||
|
||||
def test_block_add_hook_baseexception():
|
||||
# Raising BaseException will propagate out when adding a hook
|
||||
with assertRaises(BaseException):
|
||||
with TestHook(
|
||||
raise_on_events="sys.addaudithook", exc_type=BaseException
|
||||
) as hook1:
|
||||
# Adding this next hook should raise BaseException
|
||||
with TestHook() as hook2:
|
||||
pass
|
||||
|
||||
|
||||
def test_marshal():
|
||||
import marshal
|
||||
o = ("a", "b", "c", 1, 2, 3)
|
||||
payload = marshal.dumps(o)
|
||||
|
||||
with TestHook() as hook:
|
||||
assertEqual(o, marshal.loads(marshal.dumps(o)))
|
||||
|
||||
try:
|
||||
with open("test-marshal.bin", "wb") as f:
|
||||
marshal.dump(o, f)
|
||||
with open("test-marshal.bin", "rb") as f:
|
||||
assertEqual(o, marshal.load(f))
|
||||
finally:
|
||||
os.unlink("test-marshal.bin")
|
||||
|
||||
actual = [(a[0], a[1]) for e, a in hook.seen if e == "marshal.dumps"]
|
||||
assertSequenceEqual(actual, [(o, marshal.version)] * 2)
|
||||
|
||||
actual = [a[0] for e, a in hook.seen if e == "marshal.loads"]
|
||||
assertSequenceEqual(actual, [payload])
|
||||
|
||||
actual = [e for e, a in hook.seen if e == "marshal.load"]
|
||||
assertSequenceEqual(actual, ["marshal.load"])
|
||||
|
||||
|
||||
def test_pickle():
|
||||
import pickle
|
||||
|
||||
class PicklePrint:
|
||||
def __reduce_ex__(self, p):
|
||||
return str, ("Pwned!",)
|
||||
|
||||
payload_1 = pickle.dumps(PicklePrint())
|
||||
payload_2 = pickle.dumps(("a", "b", "c", 1, 2, 3))
|
||||
|
||||
# Before we add the hook, ensure our malicious pickle loads
|
||||
assertEqual("Pwned!", pickle.loads(payload_1))
|
||||
|
||||
with TestHook(raise_on_events="pickle.find_class") as hook:
|
||||
with assertRaises(RuntimeError):
|
||||
# With the hook enabled, loading globals is not allowed
|
||||
pickle.loads(payload_1)
|
||||
# pickles with no globals are okay
|
||||
pickle.loads(payload_2)
|
||||
|
||||
|
||||
def test_monkeypatch():
|
||||
class A:
|
||||
pass
|
||||
|
||||
class B:
|
||||
pass
|
||||
|
||||
class C(A):
|
||||
pass
|
||||
|
||||
a = A()
|
||||
|
||||
with TestHook() as hook:
|
||||
# Catch name changes
|
||||
C.__name__ = "X"
|
||||
# Catch type changes
|
||||
C.__bases__ = (B,)
|
||||
# Ensure bypassing __setattr__ is still caught
|
||||
type.__dict__["__bases__"].__set__(C, (B,))
|
||||
# Catch attribute replacement
|
||||
C.__init__ = B.__init__
|
||||
# Catch attribute addition
|
||||
C.new_attr = 123
|
||||
# Catch class changes
|
||||
a.__class__ = B
|
||||
|
||||
actual = [(a[0], a[1]) for e, a in hook.seen if e == "object.__setattr__"]
|
||||
assertSequenceEqual(
|
||||
[(C, "__name__"), (C, "__bases__"), (C, "__bases__"), (a, "__class__")], actual
|
||||
)
|
||||
|
||||
|
||||
def test_open(testfn):
|
||||
# SSLContext.load_dh_params uses Py_fopen() rather than normal open()
|
||||
try:
|
||||
import ssl
|
||||
|
||||
load_dh_params = ssl.create_default_context().load_dh_params
|
||||
except ImportError:
|
||||
load_dh_params = None
|
||||
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
readline = None
|
||||
|
||||
def rl(name):
|
||||
if readline:
|
||||
return getattr(readline, name, None)
|
||||
else:
|
||||
return None
|
||||
|
||||
# Try a range of "open" functions.
|
||||
# All of them should fail
|
||||
with TestHook(raise_on_events={"open"}) as hook:
|
||||
for fn, *args in [
|
||||
(open, testfn, "r"),
|
||||
(open, sys.executable, "rb"),
|
||||
(open, 3, "wb"),
|
||||
(open, testfn, "w", -1, None, None, None, False, lambda *a: 1),
|
||||
(load_dh_params, testfn),
|
||||
(rl("read_history_file"), testfn),
|
||||
(rl("read_history_file"), None),
|
||||
(rl("write_history_file"), testfn),
|
||||
(rl("write_history_file"), None),
|
||||
(rl("append_history_file"), 0, testfn),
|
||||
(rl("append_history_file"), 0, None),
|
||||
(rl("read_init_file"), testfn),
|
||||
(rl("read_init_file"), None),
|
||||
]:
|
||||
if not fn:
|
||||
continue
|
||||
with assertRaises(RuntimeError):
|
||||
try:
|
||||
fn(*args)
|
||||
except NotImplementedError:
|
||||
if fn == load_dh_params:
|
||||
# Not callable in some builds
|
||||
load_dh_params = None
|
||||
raise RuntimeError
|
||||
else:
|
||||
raise
|
||||
|
||||
actual_mode = [(a[0], a[1]) for e, a in hook.seen if e == "open" and a[1]]
|
||||
actual_flag = [(a[0], a[2]) for e, a in hook.seen if e == "open" and not a[1]]
|
||||
assertSequenceEqual(
|
||||
[
|
||||
i
|
||||
for i in [
|
||||
(testfn, "r"),
|
||||
(sys.executable, "r"),
|
||||
(3, "w"),
|
||||
(testfn, "w"),
|
||||
(testfn, "rb") if load_dh_params else None,
|
||||
(testfn, "r") if readline else None,
|
||||
("~/.history", "r") if readline else None,
|
||||
(testfn, "w") if readline else None,
|
||||
("~/.history", "w") if readline else None,
|
||||
(testfn, "a") if rl("append_history_file") else None,
|
||||
("~/.history", "a") if rl("append_history_file") else None,
|
||||
(testfn, "r") if readline else None,
|
||||
("<readline_init_file>", "r") if readline else None,
|
||||
]
|
||||
if i is not None
|
||||
],
|
||||
actual_mode,
|
||||
)
|
||||
assertSequenceEqual([], actual_flag)
|
||||
|
||||
|
||||
def test_cantrace():
|
||||
traced = []
|
||||
|
||||
def trace(frame, event, *args):
|
||||
if frame.f_code == TestHook.__call__.__code__:
|
||||
traced.append(event)
|
||||
|
||||
old = sys.settrace(trace)
|
||||
try:
|
||||
with TestHook() as hook:
|
||||
# No traced call
|
||||
eval("1")
|
||||
|
||||
# No traced call
|
||||
hook.__cantrace__ = False
|
||||
eval("2")
|
||||
|
||||
# One traced call
|
||||
hook.__cantrace__ = True
|
||||
eval("3")
|
||||
|
||||
# Two traced calls (writing to private member, eval)
|
||||
hook.__cantrace__ = 1
|
||||
eval("4")
|
||||
|
||||
# One traced call (writing to private member)
|
||||
hook.__cantrace__ = 0
|
||||
finally:
|
||||
sys.settrace(old)
|
||||
|
||||
assertSequenceEqual(["call"] * 4, traced)
|
||||
|
||||
|
||||
def test_mmap():
|
||||
import mmap
|
||||
|
||||
with TestHook() as hook:
|
||||
mmap.mmap(-1, 8)
|
||||
assertEqual(hook.seen[0][1][:2], (-1, 8))
|
||||
|
||||
|
||||
def test_ctypes_call_function():
|
||||
import ctypes
|
||||
import _ctypes
|
||||
|
||||
with TestHook() as hook:
|
||||
_ctypes.call_function(ctypes._memmove_addr, (0, 0, 0))
|
||||
assert ("ctypes.call_function", (ctypes._memmove_addr, (0, 0, 0))) in hook.seen, f"{ctypes._memmove_addr=} {hook.seen=}"
|
||||
|
||||
ctypes.CFUNCTYPE(ctypes.c_voidp)(ctypes._memset_addr)(1, 0, 0)
|
||||
assert ("ctypes.call_function", (ctypes._memset_addr, (1, 0, 0))) in hook.seen, f"{ctypes._memset_addr=} {hook.seen=}"
|
||||
|
||||
with TestHook() as hook:
|
||||
ctypes.cast(ctypes.c_voidp(0), ctypes.POINTER(ctypes.c_char))
|
||||
assert "ctypes.call_function" in hook.seen_events
|
||||
|
||||
with TestHook() as hook:
|
||||
ctypes.string_at(id("ctypes.string_at") + 40)
|
||||
assert "ctypes.call_function" in hook.seen_events
|
||||
assert "ctypes.string_at" in hook.seen_events
|
||||
|
||||
|
||||
def test_posixsubprocess():
|
||||
import multiprocessing.util
|
||||
|
||||
exe = b"xxx"
|
||||
args = [b"yyy", b"zzz"]
|
||||
with TestHook() as hook:
|
||||
multiprocessing.util.spawnv_passfds(exe, args, ())
|
||||
assert ("_posixsubprocess.fork_exec", ([exe], args, None)) in hook.seen
|
||||
|
||||
|
||||
def test_excepthook():
|
||||
def excepthook(exc_type, exc_value, exc_tb):
|
||||
if exc_type is not RuntimeError:
|
||||
sys.__excepthook__(exc_type, exc_value, exc_tb)
|
||||
|
||||
def hook(event, args):
|
||||
if event == "sys.excepthook":
|
||||
if not isinstance(args[2], args[1]):
|
||||
raise TypeError(f"Expected isinstance({args[2]!r}, " f"{args[1]!r})")
|
||||
if args[0] != excepthook:
|
||||
raise ValueError(f"Expected {args[0]} == {excepthook}")
|
||||
print(event, repr(args[2]))
|
||||
|
||||
sys.addaudithook(hook)
|
||||
sys.excepthook = excepthook
|
||||
raise RuntimeError("fatal-error")
|
||||
|
||||
|
||||
def test_unraisablehook():
|
||||
from _testcapi import err_formatunraisable
|
||||
|
||||
def unraisablehook(hookargs):
|
||||
pass
|
||||
|
||||
def hook(event, args):
|
||||
if event == "sys.unraisablehook":
|
||||
if args[0] != unraisablehook:
|
||||
raise ValueError(f"Expected {args[0]} == {unraisablehook}")
|
||||
print(event, repr(args[1].exc_value), args[1].err_msg)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
sys.unraisablehook = unraisablehook
|
||||
err_formatunraisable(RuntimeError("nonfatal-error"),
|
||||
"Exception ignored for audit hook test")
|
||||
|
||||
|
||||
def test_winreg():
|
||||
from winreg import OpenKey, EnumKey, CloseKey, HKEY_LOCAL_MACHINE
|
||||
|
||||
def hook(event, args):
|
||||
if not event.startswith("winreg."):
|
||||
return
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
|
||||
k = OpenKey(HKEY_LOCAL_MACHINE, "Software")
|
||||
EnumKey(k, 0)
|
||||
try:
|
||||
EnumKey(k, 10000)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError("Expected EnumKey(HKLM, 10000) to fail")
|
||||
|
||||
kv = k.Detach()
|
||||
CloseKey(kv)
|
||||
|
||||
|
||||
def test_socket():
|
||||
import socket
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("socket."):
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
|
||||
socket.gethostname()
|
||||
|
||||
# Don't care if this fails, we just want the audit message
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
# Don't care if this fails, we just want the audit message
|
||||
sock.bind(('127.0.0.1', 8080))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
|
||||
def test_gc():
|
||||
import gc
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("gc."):
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
|
||||
gc.get_objects(generation=1)
|
||||
|
||||
x = object()
|
||||
y = [x]
|
||||
|
||||
gc.get_referrers(x)
|
||||
gc.get_referents(y)
|
||||
|
||||
|
||||
def test_http_client():
|
||||
import http.client
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("http.client."):
|
||||
print(event, *args[1:])
|
||||
|
||||
sys.addaudithook(hook)
|
||||
|
||||
conn = http.client.HTTPConnection('www.python.org')
|
||||
try:
|
||||
conn.request('GET', '/')
|
||||
except OSError:
|
||||
print('http.client.send', '[cannot send]')
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_sqlite3():
|
||||
import sqlite3
|
||||
|
||||
def hook(event, *args):
|
||||
if event.startswith("sqlite3."):
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
cx1 = sqlite3.connect(":memory:")
|
||||
cx2 = sqlite3.Connection(":memory:")
|
||||
|
||||
# Configured without --enable-loadable-sqlite-extensions
|
||||
try:
|
||||
if hasattr(sqlite3.Connection, "enable_load_extension"):
|
||||
cx1.enable_load_extension(False)
|
||||
try:
|
||||
cx1.load_extension("test")
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError("Expected sqlite3.load_extension to fail")
|
||||
finally:
|
||||
cx1.close()
|
||||
cx2.close()
|
||||
|
||||
def test_sys_getframe():
|
||||
import sys
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("sys."):
|
||||
print(event, args[0].f_code.co_name)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
sys._getframe()
|
||||
|
||||
|
||||
def test_sys_getframemodulename():
|
||||
import sys
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("sys."):
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
sys._getframemodulename()
|
||||
|
||||
|
||||
def test_threading():
|
||||
import _thread
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith(("_thread.", "cpython.PyThreadState", "test.")):
|
||||
print(event, args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
|
||||
lock = _thread.allocate_lock()
|
||||
lock.acquire()
|
||||
|
||||
class test_func:
|
||||
def __repr__(self): return "<test_func>"
|
||||
def __call__(self):
|
||||
sys.audit("test.test_func")
|
||||
lock.release()
|
||||
|
||||
i = _thread.start_new_thread(test_func(), ())
|
||||
lock.acquire()
|
||||
|
||||
handle = _thread.start_joinable_thread(test_func())
|
||||
handle.join()
|
||||
|
||||
|
||||
def test_threading_abort():
|
||||
# Ensures that aborting PyThreadState_New raises the correct exception
|
||||
import _thread
|
||||
|
||||
class ThreadNewAbortError(Exception):
|
||||
pass
|
||||
|
||||
def hook(event, args):
|
||||
if event == "cpython.PyThreadState_New":
|
||||
raise ThreadNewAbortError()
|
||||
|
||||
sys.addaudithook(hook)
|
||||
|
||||
try:
|
||||
_thread.start_new_thread(lambda: None, ())
|
||||
except ThreadNewAbortError:
|
||||
# Other exceptions are raised and the test will fail
|
||||
pass
|
||||
|
||||
|
||||
def test_wmi_exec_query():
|
||||
import _wmi
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("_wmi."):
|
||||
print(event, args[0])
|
||||
|
||||
sys.addaudithook(hook)
|
||||
try:
|
||||
_wmi.exec_query("SELECT * FROM Win32_OperatingSystem")
|
||||
except WindowsError as e:
|
||||
# gh-112278: WMI may be slow response when first called, but we still
|
||||
# get the audit event, so just ignore the timeout
|
||||
if e.winerror != 258:
|
||||
raise
|
||||
|
||||
def test_syslog():
|
||||
import syslog
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("syslog."):
|
||||
print(event, *args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
syslog.openlog('python')
|
||||
syslog.syslog('test')
|
||||
syslog.setlogmask(syslog.LOG_DEBUG)
|
||||
syslog.closelog()
|
||||
# implicit open
|
||||
syslog.syslog('test2')
|
||||
# open with default ident
|
||||
syslog.openlog(logoption=syslog.LOG_NDELAY, facility=syslog.LOG_LOCAL0)
|
||||
sys.argv = None
|
||||
syslog.openlog()
|
||||
syslog.closelog()
|
||||
|
||||
|
||||
def test_not_in_gc():
|
||||
import gc
|
||||
|
||||
hook = lambda *a: None
|
||||
sys.addaudithook(hook)
|
||||
|
||||
for o in gc.get_objects():
|
||||
if isinstance(o, list):
|
||||
assert hook not in o
|
||||
|
||||
|
||||
def test_time(mode):
|
||||
import time
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("time."):
|
||||
if mode == 'print':
|
||||
print(event, *args)
|
||||
elif mode == 'fail':
|
||||
raise AssertionError('hook failed')
|
||||
sys.addaudithook(hook)
|
||||
|
||||
time.sleep(0)
|
||||
time.sleep(0.0625) # 1/16, a small exact float
|
||||
try:
|
||||
time.sleep(-1)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def test_sys_monitoring_register_callback():
|
||||
import sys
|
||||
|
||||
def hook(event, args):
|
||||
if event.startswith("sys.monitoring"):
|
||||
print(event, args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
sys.monitoring.register_callback(1, 1, None)
|
||||
|
||||
|
||||
def test_winapi_createnamedpipe(pipe_name):
|
||||
import _winapi
|
||||
|
||||
def hook(event, args):
|
||||
if event == "_winapi.CreateNamedPipe":
|
||||
print(event, args)
|
||||
|
||||
sys.addaudithook(hook)
|
||||
_winapi.CreateNamedPipe(pipe_name, _winapi.PIPE_ACCESS_DUPLEX, 8, 2, 0, 0, 0, 0)
|
||||
|
||||
|
||||
def test_assert_unicode():
|
||||
import sys
|
||||
sys.addaudithook(lambda *args: None)
|
||||
try:
|
||||
sys.audit(9)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError("Expected sys.audit(9) to fail.")
|
||||
|
||||
def test_sys_remote_exec():
|
||||
import tempfile
|
||||
|
||||
pid = os.getpid()
|
||||
event_pid = -1
|
||||
event_script_path = ""
|
||||
remote_event_script_path = ""
|
||||
def hook(event, args):
|
||||
if event not in ["sys.remote_exec", "cpython.remote_debugger_script"]:
|
||||
return
|
||||
print(event, args)
|
||||
match event:
|
||||
case "sys.remote_exec":
|
||||
nonlocal event_pid, event_script_path
|
||||
event_pid = args[0]
|
||||
event_script_path = args[1]
|
||||
case "cpython.remote_debugger_script":
|
||||
nonlocal remote_event_script_path
|
||||
remote_event_script_path = args[0]
|
||||
|
||||
sys.addaudithook(hook)
|
||||
with tempfile.NamedTemporaryFile(mode='w+', delete=True) as tmp_file:
|
||||
tmp_file.write("a = 1+1\n")
|
||||
tmp_file.flush()
|
||||
sys.remote_exec(pid, tmp_file.name)
|
||||
assertEqual(event_pid, pid)
|
||||
assertEqual(event_script_path, tmp_file.name)
|
||||
assertEqual(remote_event_script_path, tmp_file.name)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from test.support import suppress_msvcrt_asserts
|
||||
|
||||
suppress_msvcrt_asserts()
|
||||
|
||||
test = sys.argv[1]
|
||||
globals()[test](*sys.argv[2:])
|
||||
9
Lib/test/datetimetester.py
vendored
9
Lib/test/datetimetester.py
vendored
@@ -47,7 +47,11 @@ import _strptime
|
||||
try:
|
||||
import _pydatetime
|
||||
except ImportError:
|
||||
pass
|
||||
_pydatetime = None
|
||||
try:
|
||||
import _datetime
|
||||
except ImportError:
|
||||
_datetime = None
|
||||
#
|
||||
|
||||
pickle_loads = {pickle.loads, pickle._loads}
|
||||
@@ -3011,7 +3015,6 @@ class TestDateTime(TestDate):
|
||||
self.assertEqual(t.strftime("%z"), "-0200" + z)
|
||||
self.assertEqual(t.strftime("%:z"), "-02:00:" + z)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON")
|
||||
def test_strftime_special(self):
|
||||
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
|
||||
s1 = t.strftime('%c')
|
||||
@@ -3879,7 +3882,6 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
|
||||
# gh-85432: The parameter was named "fmt" in the pure-Python impl.
|
||||
t.strftime(format="%f")
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON")
|
||||
def test_strftime_special(self):
|
||||
t = self.theclass(1, 2, 3, 4)
|
||||
s1 = t.strftime('%I%p%Z')
|
||||
@@ -4360,7 +4362,6 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
|
||||
self.assertEqual(t.microsecond, 0)
|
||||
self.assertIsNone(t.tzinfo)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON")
|
||||
def test_zones(self):
|
||||
est = FixedOffset(-300, "EST", 1)
|
||||
utc = FixedOffset(0, "UTC", -2)
|
||||
|
||||
1
Lib/test/exception_hierarchy.txt
vendored
1
Lib/test/exception_hierarchy.txt
vendored
@@ -40,6 +40,7 @@ BaseException
|
||||
├── ReferenceError
|
||||
├── RuntimeError
|
||||
│ ├── NotImplementedError
|
||||
│ ├── PythonFinalizationError
|
||||
│ └── RecursionError
|
||||
├── StopAsyncIteration
|
||||
├── StopIteration
|
||||
|
||||
390
Lib/test/picklecommon.py
vendored
Normal file
390
Lib/test/picklecommon.py
vendored
Normal file
@@ -0,0 +1,390 @@
|
||||
# Classes used for pickle testing.
|
||||
# They are moved to separate file, so they can be loaded
|
||||
# in other Python version for test_xpickle.
|
||||
|
||||
import sys
|
||||
|
||||
class C:
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
# For test_load_classic_instance
|
||||
class D(C):
|
||||
def __init__(self, arg):
|
||||
pass
|
||||
|
||||
class E(C):
|
||||
def __getinitargs__(self):
|
||||
return ()
|
||||
|
||||
import __main__
|
||||
__main__.C = C
|
||||
C.__module__ = "__main__"
|
||||
__main__.D = D
|
||||
D.__module__ = "__main__"
|
||||
__main__.E = E
|
||||
E.__module__ = "__main__"
|
||||
|
||||
# Simple mutable object.
|
||||
class Object(object):
|
||||
pass
|
||||
|
||||
# Hashable immutable key object containing unheshable mutable data.
|
||||
class K:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __reduce__(self):
|
||||
# Shouldn't support the recursion itself
|
||||
return K, (self.value,)
|
||||
|
||||
class WithSlots(object):
|
||||
__slots__ = ('a', 'b')
|
||||
|
||||
class WithSlotsSubclass(WithSlots):
|
||||
__slots__ = ('c',)
|
||||
|
||||
class WithSlotsAndDict(object):
|
||||
__slots__ = ('a', '__dict__')
|
||||
|
||||
class WithPrivateAttrs(object):
|
||||
def __init__(self, a):
|
||||
self.__private = a
|
||||
def get(self):
|
||||
return self.__private
|
||||
|
||||
class WithPrivateAttrsSubclass(WithPrivateAttrs):
|
||||
def __init__(self, a, b):
|
||||
super().__init__(a)
|
||||
self.__private = b
|
||||
def get2(self):
|
||||
return self.__private
|
||||
|
||||
class WithPrivateSlots(object):
|
||||
__slots__ = ('__private',)
|
||||
def __init__(self, a):
|
||||
self.__private = a
|
||||
def get(self):
|
||||
return self.__private
|
||||
|
||||
class WithPrivateSlotsSubclass(WithPrivateSlots):
|
||||
__slots__ = ('__private',)
|
||||
def __init__(self, a, b):
|
||||
super().__init__(a)
|
||||
self.__private = b
|
||||
def get2(self):
|
||||
return self.__private
|
||||
|
||||
# For test_misc
|
||||
class myint(int):
|
||||
def __init__(self, x):
|
||||
self.str = str(x)
|
||||
|
||||
# For test_misc and test_getinitargs
|
||||
class initarg(C):
|
||||
|
||||
def __init__(self, a, b):
|
||||
self.a = a
|
||||
self.b = b
|
||||
|
||||
def __getinitargs__(self):
|
||||
return self.a, self.b
|
||||
|
||||
# For test_metaclass
|
||||
class metaclass(type):
|
||||
pass
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
# Syntax not compatible with Python 2
|
||||
exec('''
|
||||
class use_metaclass(object, metaclass=metaclass):
|
||||
pass
|
||||
''')
|
||||
else:
|
||||
class use_metaclass(object):
|
||||
__metaclass__ = metaclass
|
||||
|
||||
|
||||
# Test classes for reduce_ex
|
||||
|
||||
class R:
|
||||
def __init__(self, reduce=None):
|
||||
self.reduce = reduce
|
||||
def __reduce__(self, proto):
|
||||
return self.reduce
|
||||
|
||||
class REX:
|
||||
def __init__(self, reduce_ex=None):
|
||||
self.reduce_ex = reduce_ex
|
||||
def __reduce_ex__(self, proto):
|
||||
return self.reduce_ex
|
||||
|
||||
class REX_one(object):
|
||||
"""No __reduce_ex__ here, but inheriting it from object"""
|
||||
_reduce_called = 0
|
||||
def __reduce__(self):
|
||||
self._reduce_called = 1
|
||||
return REX_one, ()
|
||||
|
||||
class REX_two(object):
|
||||
"""No __reduce__ here, but inheriting it from object"""
|
||||
_proto = None
|
||||
def __reduce_ex__(self, proto):
|
||||
self._proto = proto
|
||||
return REX_two, ()
|
||||
|
||||
class REX_three(object):
|
||||
_proto = None
|
||||
def __reduce_ex__(self, proto):
|
||||
self._proto = proto
|
||||
return REX_two, ()
|
||||
def __reduce__(self):
|
||||
raise AssertionError("This __reduce__ shouldn't be called")
|
||||
|
||||
class REX_four(object):
|
||||
"""Calling base class method should succeed"""
|
||||
_proto = None
|
||||
def __reduce_ex__(self, proto):
|
||||
self._proto = proto
|
||||
return object.__reduce_ex__(self, proto)
|
||||
|
||||
class REX_five(object):
|
||||
"""This one used to fail with infinite recursion"""
|
||||
_reduce_called = 0
|
||||
def __reduce__(self):
|
||||
self._reduce_called = 1
|
||||
return object.__reduce__(self)
|
||||
|
||||
class REX_six(object):
|
||||
"""This class is used to check the 4th argument (list iterator) of
|
||||
the reduce protocol.
|
||||
"""
|
||||
def __init__(self, items=None):
|
||||
self.items = items if items is not None else []
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.items == other.items
|
||||
def append(self, item):
|
||||
self.items.append(item)
|
||||
def __reduce__(self):
|
||||
return type(self), (), None, iter(self.items), None
|
||||
|
||||
class REX_seven(object):
|
||||
"""This class is used to check the 5th argument (dict iterator) of
|
||||
the reduce protocol.
|
||||
"""
|
||||
def __init__(self, table=None):
|
||||
self.table = table if table is not None else {}
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.table == other.table
|
||||
def __setitem__(self, key, value):
|
||||
self.table[key] = value
|
||||
def __reduce__(self):
|
||||
return type(self), (), None, None, iter(self.table.items())
|
||||
|
||||
class REX_state(object):
|
||||
"""This class is used to check the 3th argument (state) of
|
||||
the reduce protocol.
|
||||
"""
|
||||
def __init__(self, state=None):
|
||||
self.state = state
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.state == other.state
|
||||
def __setstate__(self, state):
|
||||
self.state = state
|
||||
def __reduce__(self):
|
||||
return type(self), (), self.state
|
||||
|
||||
# For test_reduce_ex_None
|
||||
class REX_None:
|
||||
""" Setting __reduce_ex__ to None should fail """
|
||||
__reduce_ex__ = None
|
||||
|
||||
# For test_reduce_None
|
||||
class R_None:
|
||||
""" Setting __reduce__ to None should fail """
|
||||
__reduce__ = None
|
||||
|
||||
# For test_pickle_setstate_None
|
||||
class C_None_setstate:
|
||||
""" Setting __setstate__ to None should fail """
|
||||
def __getstate__(self):
|
||||
return 1
|
||||
|
||||
__setstate__ = None
|
||||
|
||||
|
||||
# Test classes for newobj
|
||||
|
||||
# For test_newobj_generic and test_newobj_proxies
|
||||
|
||||
class MyInt(int):
|
||||
sample = 1
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
class MyLong(int):
|
||||
sample = 1
|
||||
else:
|
||||
class MyLong(long):
|
||||
sample = long(1)
|
||||
|
||||
class MyFloat(float):
|
||||
sample = 1.0
|
||||
|
||||
class MyComplex(complex):
|
||||
sample = 1.0 + 0.0j
|
||||
|
||||
class MyStr(str):
|
||||
sample = "hello"
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
class MyUnicode(str):
|
||||
sample = "hello \u1234"
|
||||
else:
|
||||
class MyUnicode(unicode):
|
||||
sample = unicode(r"hello \u1234", "raw-unicode-escape")
|
||||
|
||||
class MyTuple(tuple):
|
||||
sample = (1, 2, 3)
|
||||
|
||||
class MyList(list):
|
||||
sample = [1, 2, 3]
|
||||
|
||||
class MyDict(dict):
|
||||
sample = {"a": 1, "b": 2}
|
||||
|
||||
class MySet(set):
|
||||
sample = {"a", "b"}
|
||||
|
||||
class MyFrozenSet(frozenset):
|
||||
sample = frozenset({"a", "b"})
|
||||
|
||||
myclasses = [MyInt, MyLong, MyFloat,
|
||||
MyComplex,
|
||||
MyStr, MyUnicode,
|
||||
MyTuple, MyList, MyDict, MySet, MyFrozenSet]
|
||||
|
||||
# For test_newobj_overridden_new
|
||||
class MyIntWithNew(int):
|
||||
def __new__(cls, value):
|
||||
raise AssertionError
|
||||
|
||||
class MyIntWithNew2(MyIntWithNew):
|
||||
__new__ = int.__new__
|
||||
|
||||
|
||||
# For test_newobj_list_slots
|
||||
class SlotList(MyList):
|
||||
__slots__ = ["foo"]
|
||||
|
||||
# Ruff "redefined while unused" false positive here due to `global` variables
|
||||
# being assigned (and then restored) from within test methods earlier in the file
|
||||
class SimpleNewObj(int): # noqa: F811
|
||||
def __init__(self, *args, **kwargs):
|
||||
# raise an error, to make sure this isn't called
|
||||
raise TypeError("SimpleNewObj.__init__() didn't expect to get called")
|
||||
def __eq__(self, other):
|
||||
return int(self) == int(other) and self.__dict__ == other.__dict__
|
||||
|
||||
class ComplexNewObj(SimpleNewObj):
|
||||
def __getnewargs__(self):
|
||||
return ('%X' % self, 16)
|
||||
|
||||
class ComplexNewObjEx(SimpleNewObj):
|
||||
def __getnewargs_ex__(self):
|
||||
return ('%X' % self,), {'base': 16}
|
||||
|
||||
|
||||
class ZeroCopyBytes(bytes):
|
||||
readonly = True
|
||||
c_contiguous = True
|
||||
f_contiguous = True
|
||||
zero_copy_reconstruct = True
|
||||
|
||||
def __reduce_ex__(self, protocol):
|
||||
if protocol >= 5:
|
||||
import pickle
|
||||
return type(self)._reconstruct, (pickle.PickleBuffer(self),), None
|
||||
else:
|
||||
return type(self)._reconstruct, (bytes(self),)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({!r})".format(self.__class__.__name__, bytes(self))
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
@classmethod
|
||||
def _reconstruct(cls, obj):
|
||||
with memoryview(obj) as m:
|
||||
obj = m.obj
|
||||
if type(obj) is cls:
|
||||
# Zero-copy
|
||||
return obj
|
||||
else:
|
||||
return cls(obj)
|
||||
|
||||
|
||||
class ZeroCopyBytearray(bytearray):
|
||||
readonly = False
|
||||
c_contiguous = True
|
||||
f_contiguous = True
|
||||
zero_copy_reconstruct = True
|
||||
|
||||
def __reduce_ex__(self, protocol):
|
||||
if protocol >= 5:
|
||||
import pickle
|
||||
return type(self)._reconstruct, (pickle.PickleBuffer(self),), None
|
||||
else:
|
||||
return type(self)._reconstruct, (bytes(self),)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({!r})".format(self.__class__.__name__, bytes(self))
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
@classmethod
|
||||
def _reconstruct(cls, obj):
|
||||
with memoryview(obj) as m:
|
||||
obj = m.obj
|
||||
if type(obj) is cls:
|
||||
# Zero-copy
|
||||
return obj
|
||||
else:
|
||||
return cls(obj)
|
||||
|
||||
|
||||
# For test_nested_names
|
||||
class Nested:
|
||||
class A:
|
||||
class B:
|
||||
class C:
|
||||
pass
|
||||
|
||||
# For test_py_methods
|
||||
class PyMethodsTest:
|
||||
@staticmethod
|
||||
def cheese():
|
||||
return "cheese"
|
||||
@classmethod
|
||||
def wine(cls):
|
||||
assert cls is PyMethodsTest
|
||||
return "wine"
|
||||
def biscuits(self):
|
||||
assert isinstance(self, PyMethodsTest)
|
||||
return "biscuits"
|
||||
class Nested:
|
||||
"Nested class"
|
||||
@staticmethod
|
||||
def ketchup():
|
||||
return "ketchup"
|
||||
@classmethod
|
||||
def maple(cls):
|
||||
assert cls is PyMethodsTest.Nested
|
||||
return "maple"
|
||||
def pie(self):
|
||||
assert isinstance(self, PyMethodsTest.Nested)
|
||||
return "pie"
|
||||
|
||||
# For test_c_methods
|
||||
class Subclass(tuple):
|
||||
class Nested(str):
|
||||
pass
|
||||
1152
Lib/test/pickletester.py
vendored
1152
Lib/test/pickletester.py
vendored
File diff suppressed because it is too large
Load Diff
BIN
Lib/test/pstats.pck
vendored
Normal file
BIN
Lib/test/pstats.pck
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user