mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Compare commits
383 Commits
copilot/ad
...
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 | ||
|
|
b9cbd5133b | ||
|
|
fab1c0cc01 | ||
|
|
5dd88ee5ae | ||
|
|
2722bc06de |
@@ -4,6 +4,8 @@ argdefs
|
|||||||
argtypes
|
argtypes
|
||||||
asdl
|
asdl
|
||||||
asname
|
asname
|
||||||
|
atopen
|
||||||
|
atext
|
||||||
attro
|
attro
|
||||||
augassign
|
augassign
|
||||||
badcert
|
badcert
|
||||||
@@ -30,9 +32,12 @@ cellvar
|
|||||||
cellvars
|
cellvars
|
||||||
ceval
|
ceval
|
||||||
cfield
|
cfield
|
||||||
|
cfws
|
||||||
|
CFWS
|
||||||
CLASSDEREF
|
CLASSDEREF
|
||||||
classdict
|
classdict
|
||||||
cmpop
|
cmpop
|
||||||
|
CNOTAB
|
||||||
codedepth
|
codedepth
|
||||||
CODEUNIT
|
CODEUNIT
|
||||||
CONIN
|
CONIN
|
||||||
@@ -47,13 +52,16 @@ datastack
|
|||||||
defaultdict
|
defaultdict
|
||||||
denom
|
denom
|
||||||
deopt
|
deopt
|
||||||
|
deopts
|
||||||
dictbytype
|
dictbytype
|
||||||
DICTFLAG
|
DICTFLAG
|
||||||
dictoffset
|
dictoffset
|
||||||
distpoint
|
distpoint
|
||||||
dynload
|
dynload
|
||||||
elts
|
elts
|
||||||
|
eooh
|
||||||
eofs
|
eofs
|
||||||
|
EOOH
|
||||||
evalloop
|
evalloop
|
||||||
excepthandler
|
excepthandler
|
||||||
exceptiontable
|
exceptiontable
|
||||||
@@ -62,6 +70,7 @@ fastlocals
|
|||||||
fblock
|
fblock
|
||||||
fblocks
|
fblocks
|
||||||
fdescr
|
fdescr
|
||||||
|
fdst
|
||||||
ffi_argtypes
|
ffi_argtypes
|
||||||
fielddesc
|
fielddesc
|
||||||
fieldlist
|
fieldlist
|
||||||
@@ -75,6 +84,7 @@ freelist
|
|||||||
freevar
|
freevar
|
||||||
freevars
|
freevars
|
||||||
fromlist
|
fromlist
|
||||||
|
fsrc
|
||||||
getdict
|
getdict
|
||||||
getfunc
|
getfunc
|
||||||
getiter
|
getiter
|
||||||
@@ -89,28 +99,39 @@ HASUNION
|
|||||||
heaptype
|
heaptype
|
||||||
hexdigit
|
hexdigit
|
||||||
HIGHRES
|
HIGHRES
|
||||||
|
ialloc
|
||||||
IFUNC
|
IFUNC
|
||||||
IMMUTABLETYPE
|
IMMUTABLETYPE
|
||||||
INCREF
|
INCREF
|
||||||
inlinedepth
|
inlinedepth
|
||||||
inplace
|
inplace
|
||||||
|
inpos
|
||||||
|
ioffset
|
||||||
|
isbytecode
|
||||||
|
ishidden
|
||||||
ismine
|
ismine
|
||||||
ISPOINTER
|
ISPOINTER
|
||||||
|
isoctal
|
||||||
iteminfo
|
iteminfo
|
||||||
Itertool
|
Itertool
|
||||||
|
iused
|
||||||
keeped
|
keeped
|
||||||
kwnames
|
kwnames
|
||||||
kwonlyarg
|
kwonlyarg
|
||||||
kwonlyargs
|
kwonlyargs
|
||||||
|
kwonlydefaults
|
||||||
lasti
|
lasti
|
||||||
libffi
|
libffi
|
||||||
linearise
|
linearise
|
||||||
|
lineful
|
||||||
lineiterator
|
lineiterator
|
||||||
linetable
|
linetable
|
||||||
|
LNOTAB
|
||||||
loadfast
|
loadfast
|
||||||
localsplus
|
localsplus
|
||||||
localspluskinds
|
localspluskinds
|
||||||
Lshift
|
Lshift
|
||||||
|
lslpp
|
||||||
lsprof
|
lsprof
|
||||||
MAXBLOCKS
|
MAXBLOCKS
|
||||||
maxdepth
|
maxdepth
|
||||||
@@ -120,16 +141,23 @@ mult
|
|||||||
multibytecodec
|
multibytecodec
|
||||||
nameobj
|
nameobj
|
||||||
nameop
|
nameop
|
||||||
|
nargsf
|
||||||
|
nblocks
|
||||||
ncells
|
ncells
|
||||||
|
ncellsused
|
||||||
|
ncellvars
|
||||||
nconsts
|
nconsts
|
||||||
newargs
|
newargs
|
||||||
newfree
|
newfree
|
||||||
NEWLOCALS
|
NEWLOCALS
|
||||||
newsemlockobject
|
newsemlockobject
|
||||||
|
nextop
|
||||||
nfrees
|
nfrees
|
||||||
nkwargs
|
nkwargs
|
||||||
nkwelts
|
nkwelts
|
||||||
nlocalsplus
|
nlocalsplus
|
||||||
|
nointerrupt
|
||||||
|
noffsets
|
||||||
Nondescriptor
|
Nondescriptor
|
||||||
noninteger
|
noninteger
|
||||||
nops
|
nops
|
||||||
@@ -137,6 +165,7 @@ noraise
|
|||||||
nseen
|
nseen
|
||||||
NSIGNALS
|
NSIGNALS
|
||||||
numer
|
numer
|
||||||
|
nvars
|
||||||
opname
|
opname
|
||||||
opnames
|
opnames
|
||||||
orelse
|
orelse
|
||||||
@@ -149,18 +178,22 @@ patma
|
|||||||
peepholer
|
peepholer
|
||||||
phcount
|
phcount
|
||||||
platstdlib
|
platstdlib
|
||||||
|
ploc
|
||||||
posonlyarg
|
posonlyarg
|
||||||
posonlyargs
|
posonlyargs
|
||||||
prec
|
prec
|
||||||
|
preds
|
||||||
preinitialized
|
preinitialized
|
||||||
pybuilddir
|
pybuilddir
|
||||||
pycore
|
pycore
|
||||||
pyinner
|
pyinner
|
||||||
pydecimal
|
pydecimal
|
||||||
|
pyerrors
|
||||||
Pyfunc
|
Pyfunc
|
||||||
pylifecycle
|
pylifecycle
|
||||||
pymain
|
pymain
|
||||||
pyrepl
|
pyrepl
|
||||||
|
pystate
|
||||||
PYTHONTRACEMALLOC
|
PYTHONTRACEMALLOC
|
||||||
PYTHONUTF8
|
PYTHONUTF8
|
||||||
pythonw
|
pythonw
|
||||||
@@ -168,6 +201,7 @@ PYTHREAD_NAME
|
|||||||
releasebuffer
|
releasebuffer
|
||||||
repr
|
repr
|
||||||
resinfo
|
resinfo
|
||||||
|
retarget
|
||||||
Rshift
|
Rshift
|
||||||
SA_ONSTACK
|
SA_ONSTACK
|
||||||
saveall
|
saveall
|
||||||
@@ -179,6 +213,7 @@ SETREF
|
|||||||
setresult
|
setresult
|
||||||
setslice
|
setslice
|
||||||
settraceallthreads
|
settraceallthreads
|
||||||
|
sget
|
||||||
SLOTDEFINED
|
SLOTDEFINED
|
||||||
SMALLBUF
|
SMALLBUF
|
||||||
SOABI
|
SOABI
|
||||||
@@ -189,13 +224,16 @@ staticbase
|
|||||||
stginfo
|
stginfo
|
||||||
storefast
|
storefast
|
||||||
stringlib
|
stringlib
|
||||||
|
stringized
|
||||||
structseq
|
structseq
|
||||||
subkwargs
|
subkwargs
|
||||||
subparams
|
subparams
|
||||||
subscr
|
subscr
|
||||||
sval
|
sval
|
||||||
swappedbytes
|
swappedbytes
|
||||||
|
swaptimize
|
||||||
sysdict
|
sysdict
|
||||||
|
tbstderr
|
||||||
templatelib
|
templatelib
|
||||||
testconsole
|
testconsole
|
||||||
threadstate
|
threadstate
|
||||||
@@ -214,6 +252,7 @@ uncollectable
|
|||||||
Unhandle
|
Unhandle
|
||||||
unparse
|
unparse
|
||||||
unparser
|
unparser
|
||||||
|
untargeted
|
||||||
untracking
|
untracking
|
||||||
VARKEYWORDS
|
VARKEYWORDS
|
||||||
varkwarg
|
varkwarg
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ fillchar
|
|||||||
fillvalue
|
fillvalue
|
||||||
finallyhandler
|
finallyhandler
|
||||||
firstiter
|
firstiter
|
||||||
|
fobj
|
||||||
firstlineno
|
firstlineno
|
||||||
fnctl
|
fnctl
|
||||||
frombytes
|
frombytes
|
||||||
@@ -111,12 +112,14 @@ idfunc
|
|||||||
idiv
|
idiv
|
||||||
idxs
|
idxs
|
||||||
impls
|
impls
|
||||||
|
infd
|
||||||
indexgroup
|
indexgroup
|
||||||
infj
|
infj
|
||||||
inittab
|
inittab
|
||||||
Inittab
|
Inittab
|
||||||
instancecheck
|
instancecheck
|
||||||
instanceof
|
instanceof
|
||||||
|
instrs
|
||||||
interpchannels
|
interpchannels
|
||||||
interpqueues
|
interpqueues
|
||||||
irepeat
|
irepeat
|
||||||
@@ -175,6 +178,7 @@ Nonprintable
|
|||||||
onceregistry
|
onceregistry
|
||||||
origname
|
origname
|
||||||
ospath
|
ospath
|
||||||
|
outfd
|
||||||
pendingcr
|
pendingcr
|
||||||
phello
|
phello
|
||||||
platlibdir
|
platlibdir
|
||||||
@@ -185,10 +189,12 @@ posonlyargcount
|
|||||||
prepending
|
prepending
|
||||||
profilefunc
|
profilefunc
|
||||||
pycache
|
pycache
|
||||||
|
pycapsule
|
||||||
pycodecs
|
pycodecs
|
||||||
pycs
|
pycs
|
||||||
pydatetime
|
pydatetime
|
||||||
pyexpat
|
pyexpat
|
||||||
|
PYGILSTATE
|
||||||
pyio
|
pyio
|
||||||
pymain
|
pymain
|
||||||
PYTHONAPI
|
PYTHONAPI
|
||||||
|
|||||||
12
.cspell.json
12
.cspell.json
@@ -49,20 +49,25 @@
|
|||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"**/__pycache__/**",
|
"**/__pycache__/**",
|
||||||
"target/**",
|
"target/**",
|
||||||
"Lib/**"
|
"Lib/**",
|
||||||
|
"crates/host_env/**"
|
||||||
],
|
],
|
||||||
// words - list of words to be always considered correct
|
// words - list of words to be always considered correct
|
||||||
// (compound words like pyarg, baseclass, microbenchmark are handled by allowCompoundWords)
|
// (compound words like pyarg, baseclass, microbenchmark are handled by allowCompoundWords)
|
||||||
"words": [
|
"words": [
|
||||||
"aiterable",
|
"aiterable",
|
||||||
"alnum",
|
"alnum",
|
||||||
|
"csock",
|
||||||
"coro",
|
"coro",
|
||||||
"dedentations",
|
"dedentations",
|
||||||
"dedents",
|
"dedents",
|
||||||
"deduped",
|
"deduped",
|
||||||
|
"deoptimized",
|
||||||
"deoptimize",
|
"deoptimize",
|
||||||
"emscripten",
|
"emscripten",
|
||||||
"excs",
|
"excs",
|
||||||
|
"fnfe",
|
||||||
|
"ifexp",
|
||||||
"interps",
|
"interps",
|
||||||
"jitted",
|
"jitted",
|
||||||
"jitting",
|
"jitting",
|
||||||
@@ -72,8 +77,13 @@
|
|||||||
"oparg",
|
"oparg",
|
||||||
"opargs",
|
"opargs",
|
||||||
"pyc",
|
"pyc",
|
||||||
|
"reborrow",
|
||||||
|
"reraises",
|
||||||
|
"reraising",
|
||||||
"significand",
|
"significand",
|
||||||
"summands",
|
"summands",
|
||||||
|
"TESTFN",
|
||||||
|
"TZPATH",
|
||||||
"unraisable",
|
"unraisable",
|
||||||
"wasi",
|
"wasi",
|
||||||
"weaked",
|
"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
|
[attr]generated linguist-generated=true diff=generated
|
||||||
|
|
||||||
Lib/_opcode_metadata.py generated
|
Lib/_opcode_metadata.py generated
|
||||||
Lib/keyword.py generated
|
Lib/keyword.py generated
|
||||||
Lib/idlelib/help.html generated
|
Lib/idlelib/help.html generated
|
||||||
Lib/test/certdata/*.pem generated
|
Lib/test/certdata/*.pem generated
|
||||||
Lib/test/certdata/*.0 generated
|
Lib/test/certdata/*.0 generated
|
||||||
Lib/test/levenshtein_examples.json generated
|
Lib/test/levenshtein_examples.json generated
|
||||||
Lib/test/test_stable_abi_ctypes.py generated
|
Lib/test/test_stable_abi_ctypes.py generated
|
||||||
Lib/token.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? -->
|
||||||
|
|
||||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -2,7 +2,9 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: cargo
|
- package-ecosystem: cargo
|
||||||
directory: /
|
directories:
|
||||||
|
- "/"
|
||||||
|
- "crates/*"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
cooldown:
|
cooldown:
|
||||||
@@ -19,6 +21,7 @@ updates:
|
|||||||
- "digest"
|
- "digest"
|
||||||
- "md-5"
|
- "md-5"
|
||||||
- "sha-1"
|
- "sha-1"
|
||||||
|
- "sha1"
|
||||||
- "sha2"
|
- "sha2"
|
||||||
- "sha3"
|
- "sha3"
|
||||||
- "blake2"
|
- "blake2"
|
||||||
@@ -120,6 +123,11 @@ updates:
|
|||||||
toml:
|
toml:
|
||||||
patterns:
|
patterns:
|
||||||
- "toml*"
|
- "toml*"
|
||||||
|
unix:
|
||||||
|
patterns:
|
||||||
|
- "mac_address"
|
||||||
|
- "nix"
|
||||||
|
- "rustyline"
|
||||||
wasm-bindgen:
|
wasm-bindgen:
|
||||||
patterns:
|
patterns:
|
||||||
- "wasm-bindgen*"
|
- "wasm-bindgen*"
|
||||||
|
|||||||
451
.github/workflows/ci.yaml
vendored
451
.github/workflows/ci.yaml
vendored
@@ -19,21 +19,63 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
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
|
CARGO_ARGS_NO_SSL: --no-default-features --features stdlib,importlib,stdio,encodings,sqlite,host_env
|
||||||
# Crates excluded from workspace builds:
|
# Crates excluded from workspace builds:
|
||||||
# - rustpython_wasm: requires wasm target
|
# - rustpython_wasm: requires wasm target
|
||||||
# - rustpython-compiler-source: deprecated
|
# - rustpython-compiler-source: deprecated
|
||||||
# - rustpython-venvlauncher: Windows-only
|
# - rustpython-venvlauncher: Windows-only
|
||||||
WORKSPACE_EXCLUDES: --exclude rustpython_wasm --exclude rustpython-compiler-source --exclude rustpython-venvlauncher
|
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_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
|
||||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
|
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
|
CARGO_PROFILE_TEST_DEBUG: 0
|
||||||
|
CARGO_PROFILE_DEV_DEBUG: 0
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG: 0
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # TODO: Remove on 2026/06/02
|
||||||
|
CI: true
|
||||||
|
|
||||||
jobs:
|
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:
|
rust_tests:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
env:
|
env:
|
||||||
@@ -51,27 +93,35 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- 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:
|
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
|
- name: Install macOS dependencies
|
||||||
uses: ./.github/actions/install-macos-deps
|
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
|
- 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
|
- name: run c-api tests
|
||||||
run: cargo check ${{ env.CARGO_ARGS }}
|
working-directory: crates/capi
|
||||||
|
run: cargo test
|
||||||
- run: cargo doc --locked
|
if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows
|
||||||
if: runner.os == 'Linux'
|
|
||||||
|
|
||||||
- name: check compilation without host_env (sandbox mode)
|
- name: check compilation without host_env (sandbox mode)
|
||||||
run: |
|
run: |
|
||||||
@@ -90,6 +140,10 @@ jobs:
|
|||||||
run: cargo build --no-default-features --features ssl-openssl
|
run: cargo build --no-default-features --features ssl-openssl
|
||||||
if: runner.os == 'Linux'
|
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
|
# - name: Install tk-dev for tkinter build
|
||||||
# run: sudo apt-get update && sudo apt-get install -y tk-dev
|
# run: sudo apt-get update && sudo apt-get install -y tk-dev
|
||||||
# if: runner.os == 'Linux'
|
# if: runner.os == 'Linux'
|
||||||
@@ -111,12 +165,18 @@ jobs:
|
|||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
|
|
||||||
cargo_check:
|
cargo_check:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
|
||||||
name: cargo check
|
name: cargo check
|
||||||
runs-on: ${{ matrix.os }}
|
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:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: aarch64-linux-android
|
target: aarch64-linux-android
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
@@ -127,10 +187,13 @@ jobs:
|
|||||||
target: i686-unknown-linux-musl
|
target: i686-unknown-linux-musl
|
||||||
dependencies:
|
dependencies:
|
||||||
musl-tools: true
|
musl-tools: true
|
||||||
|
skip_ssl: true
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: wasm32-wasip2
|
target: wasm32-wasip2
|
||||||
|
skip_ssl: true
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: x86_64-unknown-freebsd
|
target: x86_64-unknown-freebsd
|
||||||
|
skip_ssl: true
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-gnu
|
target: aarch64-unknown-linux-gnu
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -155,8 +218,7 @@ jobs:
|
|||||||
gcc-aarch64-linux-gnu: ${{ matrix.dependencies.gcc-aarch64-linux-gnu || false }}
|
gcc-aarch64-linux-gnu: ${{ matrix.dependencies.gcc-aarch64-linux-gnu || false }}
|
||||||
|
|
||||||
- name: Restore cache
|
- name: Restore cache
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
if: ${{ github.ref != 'refs/heads/main' }} # Never restore on main
|
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -165,9 +227,12 @@ jobs:
|
|||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
# key won't match, will rely on restore-keys
|
# key won't match, will rely on restore-keys
|
||||||
key: cargo-check-${{ runner.os }}-${{ matrix.target }}
|
key: ${{ runner.os }}-${{ matrix.target }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
cargo-check-${{ runner.os }}-${{ matrix.target }}-
|
${{ 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
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -176,11 +241,25 @@ jobs:
|
|||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: ${{ matrix.target == 'aarch64-linux-android' }}
|
if: ${{ matrix.target == 'aarch64-linux-android' }}
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
||||||
with:
|
with:
|
||||||
ndk-version: r27
|
ndk-version: r27
|
||||||
add-to-path: true
|
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
|
# - name: Prepare repository for redox compilation
|
||||||
# run: bash scripts/redox/uncomment-cargo.sh
|
# run: bash scripts/redox/uncomment-cargo.sh
|
||||||
# - name: Check compilation for Redox
|
# - name: Check compilation for Redox
|
||||||
@@ -189,24 +268,12 @@ jobs:
|
|||||||
# command: check
|
# command: check
|
||||||
# args: --ignore-rust-version
|
# args: --ignore-rust-version
|
||||||
|
|
||||||
- name: Check compilation
|
- name: Check compilation with threading
|
||||||
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS_NO_SSL }}
|
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS_NO_SSL }} --features threading
|
||||||
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: Save cache
|
- name: Check compilation with ssl
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} # only save on main
|
if: ${{ !matrix.skip_ssl }}
|
||||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
run: cargo check --target "${{ matrix.target }}" ${{ env.CARGO_ARGS }}
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/bin/
|
|
||||||
~/.cargo/registry/index/
|
|
||||||
~/.cargo/registry/cache/
|
|
||||||
~/.cargo/git/db/
|
|
||||||
target/
|
|
||||||
key: cargo-check-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml') }}-${{ hashFiles('Cargo.lock') }}-${{ github.sha }}
|
|
||||||
|
|
||||||
snippets_cpython:
|
snippets_cpython:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
@@ -228,18 +295,21 @@ jobs:
|
|||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
extra_test_args:
|
extra_test_args:
|
||||||
- '-u all'
|
- '-u all'
|
||||||
env_polluting_tests: []
|
env_polluting_tests:
|
||||||
|
- test_set
|
||||||
skips: []
|
skips: []
|
||||||
timeout: 50
|
timeout: 50
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
extra_test_args:
|
extra_test_args:
|
||||||
- '-u all'
|
- '-u all'
|
||||||
env_polluting_tests: []
|
env_polluting_tests:
|
||||||
|
- test_set
|
||||||
skips: []
|
skips: []
|
||||||
timeout: 60
|
timeout: 60
|
||||||
- os: windows-2025
|
- os: windows-2025
|
||||||
extra_test_args: [] # TODO: Enable '-u all'
|
extra_test_args: [] # TODO: Enable '-u all'
|
||||||
env_polluting_tests: []
|
env_polluting_tests:
|
||||||
|
- test_set
|
||||||
skips:
|
skips:
|
||||||
- test_rlcompleter
|
- test_rlcompleter
|
||||||
- test_pathlib # panic by surrogate chars
|
- test_pathlib # panic by surrogate chars
|
||||||
@@ -254,13 +324,23 @@ jobs:
|
|||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- 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:
|
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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- name: Install macOS dependencies
|
- name: Install macOS dependencies
|
||||||
uses: ./.github/actions/install-macos-deps
|
uses: ./.github/actions/install-macos-deps
|
||||||
@@ -290,8 +370,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Run flaky MP CPython tests
|
- name: Run flaky MP CPython tests
|
||||||
run: |
|
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 }}
|
timeout-minutes: ${{ matrix.timeout }}
|
||||||
|
shell: bash
|
||||||
env:
|
env:
|
||||||
RUSTPYTHON_SKIP_ENV_POLLUTERS: true
|
RUSTPYTHON_SKIP_ENV_POLLUTERS: true
|
||||||
|
|
||||||
@@ -348,12 +444,82 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: python -I scripts/whats_left.py ${{ env.CARGO_ARGS }} --features jit
|
run: python -I scripts/whats_left.py ${{ env.CARGO_ARGS }} --features jit
|
||||||
|
|
||||||
|
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: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
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--
|
||||||
|
# Windows runners randomly crashes, https://github.com/actions/cache/issues/1754
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
run: cargo clippy --keep-going ${{ env.CARGO_ARGS }} --workspace --all-targets ${{ env.WORKSPACE_EXCLUDES }} -- -Dwarnings
|
||||||
|
|
||||||
|
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:
|
||||||
|
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:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
checks: write
|
checks: write
|
||||||
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
security-events: write # for zizmor
|
security-events: write # for zizmor
|
||||||
steps:
|
steps:
|
||||||
@@ -362,54 +528,76 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
|
|
||||||
- uses: cargo-bins/cargo-binstall@113a77a4ce971c41332f2129c3d995df993cf746 # v1.17.8
|
|
||||||
|
|
||||||
- name: cargo shear
|
|
||||||
run: |
|
|
||||||
cargo binstall --no-confirm cargo-shear
|
|
||||||
cargo shear
|
|
||||||
|
|
||||||
- name: actionlint
|
- name: actionlint
|
||||||
uses: reviewdog/action-actionlint@0d952c597ef8459f634d7145b0b044a9699e5e43 # v1.71.0
|
uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0
|
||||||
|
|
||||||
- name: zizmor
|
- name: zizmor
|
||||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||||
|
|
||||||
- name: restore prek cache
|
- name: restore prek cache
|
||||||
if: ${{ github.ref != 'refs/heads/main' }} # never restore on main
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
|
||||||
with:
|
with:
|
||||||
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
path: ~/.cache/prek
|
path: ~/.cache/prek
|
||||||
|
|
||||||
- name: 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
|
id: prek
|
||||||
uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1
|
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
||||||
with:
|
with:
|
||||||
cache: false
|
cache: false
|
||||||
show-verbose-logs: false
|
show-verbose-logs: false
|
||||||
continue-on-error: true
|
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
|
- name: save prek cache
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} # only save on main
|
if: ${{ github.ref == 'refs/heads/main' }} # only save on main
|
||||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
path: ~/.cache/prek
|
path: ~/.cache/prek
|
||||||
|
|
||||||
|
- name: restore git permissions
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
run: sudo chown -R "$(id -u):$(id -g)" .git
|
||||||
|
|
||||||
- name: reviewdog
|
- name: reviewdog
|
||||||
uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # 1.24.0
|
if: ${{ !cancelled() }}
|
||||||
|
uses: reviewdog/action-suggester@aa38384ceb608d00f84b4690cacc83a5aba307ff # v1.24.0
|
||||||
with:
|
with:
|
||||||
level: warning
|
level: warning
|
||||||
fail_level: error
|
fail_level: error
|
||||||
cleanup: false
|
|
||||||
|
|
||||||
miri:
|
miri:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
@@ -428,9 +616,18 @@ jobs:
|
|||||||
toolchain: ${{ env.NIGHTLY_CHANNEL }}
|
toolchain: ${{ env.NIGHTLY_CHANNEL }}
|
||||||
components: miri
|
components: miri
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
- name: Restore cache
|
||||||
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
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
|
- name: Run tests under miri
|
||||||
run: cargo +${{ env.NIGHTLY_CHANNEL }} miri test -p rustpython-vm -- miri_test
|
run: cargo +${{ env.NIGHTLY_CHANNEL }} miri test -p rustpython-vm -- miri_test
|
||||||
@@ -453,12 +650,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
- name: Restore cache
|
||||||
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
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--
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: cargo clippy --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings
|
run: cargo clippy --keep-going --manifest-path=crates/wasm/Cargo.toml -- -Dwarnings
|
||||||
|
|
||||||
- name: install wasm-pack
|
- name: install wasm-pack
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
@@ -469,15 +677,29 @@ jobs:
|
|||||||
tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver
|
tar -xzf geckodriver-v0.36.0-linux64.tar.gz -C geckodriver
|
||||||
|
|
||||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- run: python -m pip install -r requirements.txt
|
- run: python -m pip install -r requirements.txt
|
||||||
working-directory: ./wasm/tests
|
working-directory: ./wasm/tests
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
|
||||||
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
cache: "npm"
|
package-manager-cache: false
|
||||||
cache-dependency-path: "wasm/demo/package-lock.json"
|
|
||||||
|
- 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
|
- name: run test
|
||||||
run: |
|
run: |
|
||||||
driver_path="$(pwd)/../../geckodriver"
|
driver_path="$(pwd)/../../geckodriver"
|
||||||
@@ -487,8 +709,11 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||||
working-directory: ./wasm/demo
|
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
|
- name: check wasm32-unknown without js
|
||||||
run: |
|
run: |
|
||||||
cd example_projects/wasm32_without_js/rustpython-without-js
|
cd example_projects/wasm32_without_js/rustpython-without-js
|
||||||
@@ -498,6 +723,7 @@ jobs:
|
|||||||
echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2
|
echo "ERROR: wasm32-unknown module expects imports from the host environment" >&2
|
||||||
fi
|
fi
|
||||||
cargo run --release --manifest-path wasm-runtime/Cargo.toml rustpython-without-js/target/wasm32-unknown-unknown/debug/rustpython_without_js.wasm
|
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
|
- name: build notebook demo
|
||||||
if: github.ref == 'refs/heads/release'
|
if: github.ref == 'refs/heads/release'
|
||||||
run: |
|
run: |
|
||||||
@@ -507,15 +733,24 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--openssl-legacy-provider"
|
NODE_OPTIONS: "--openssl-legacy-provider"
|
||||||
working-directory: ./wasm/notebook
|
working-directory: ./wasm/notebook
|
||||||
|
|
||||||
- name: Deploy demo to Github Pages
|
- name: Deploy demo to Github Pages
|
||||||
if: success() && github.ref == 'refs/heads/release'
|
if: success() && github.ref == 'refs/heads/release'
|
||||||
uses: peaceiris/actions-gh-pages@v4
|
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||||
env:
|
env:
|
||||||
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||||
PUBLISH_DIR: ./wasm/demo/dist
|
PUBLISH_DIR: ./wasm/demo/dist
|
||||||
EXTERNAL_REPOSITORY: RustPython/demo
|
EXTERNAL_REPOSITORY: RustPython/demo
|
||||||
PUBLISH_BRANCH: master
|
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:
|
wasm-wasi:
|
||||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip:ci') }}
|
||||||
name: Run snippets and cpython tests on wasm-wasi
|
name: Run snippets and cpython tests on wasm-wasi
|
||||||
@@ -530,12 +765,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
target: wasm32-wasip1
|
target: wasm32-wasip1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
- name: Restore cache
|
||||||
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
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
|
- name: Setup Wasmer
|
||||||
uses: wasmerio/setup-wasmer@v3
|
uses: wasmerio/setup-wasmer@24b15c95293d23f89c68bd40dac76338f773e924 # v3.1
|
||||||
|
|
||||||
- name: Install clang
|
- name: Install clang
|
||||||
uses: ./.github/actions/install-linux-deps
|
uses: ./.github/actions/install-linux-deps
|
||||||
@@ -543,8 +790,44 @@ jobs:
|
|||||||
clang: true
|
clang: true
|
||||||
|
|
||||||
- name: build rustpython
|
- 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
|
- 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
|
- 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_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: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- 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--
|
||||||
|
|
||||||
|
- name: cargo doc
|
||||||
|
run: cargo doc --locked
|
||||||
|
|||||||
50
.github/workflows/cron-ci.yaml
vendored
50
.github/workflows/cron-ci.yaml
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
name: Periodic checks/tasks
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * 6"
|
- cron: "0 0 * * 6"
|
||||||
@@ -5,15 +7,15 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/cron-ci.yaml
|
- .github/workflows/cron-ci.yaml
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/cron-ci.yaml
|
- .github/workflows/cron-ci.yaml
|
||||||
|
|
||||||
name: Periodic checks/tasks
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls,jit,host_env
|
CARGO_ARGS: --no-default-features --features stdlib,importlib,stdio,encodings,ssl-rustls-aws-lc,jit,host_env
|
||||||
PYTHON_VERSION: "3.14.3"
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' # TODO: Remove on 2026/06/02
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
|
||||||
@@ -29,24 +31,32 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- 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:
|
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
|
- run: sudo apt-get update && sudo apt-get -y install lcov
|
||||||
|
|
||||||
- name: Run cargo-llvm-cov with Rust tests.
|
- 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,stdio,encodings,ssl-rustls,jit,host_env
|
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.
|
- name: Run cargo-llvm-cov with Python snippets.
|
||||||
run: python scripts/cargo-llvm-cov.py
|
run: python scripts/cargo-llvm-cov.py
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Run cargo-llvm-cov with Python test suite.
|
- name: Run cargo-llvm-cov with Python test suite.
|
||||||
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
|
run: cargo llvm-cov --no-report run -- -m test -u all --slowest --fail-env-changed
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Prepare code coverage data
|
- name: Prepare code coverage data
|
||||||
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
|
run: cargo llvm-cov report --lcov --output-path='codecov.lcov'
|
||||||
|
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||||
with:
|
with:
|
||||||
files: ./codecov.lcov
|
files: ./codecov.lcov
|
||||||
|
|
||||||
@@ -61,12 +71,15 @@ jobs:
|
|||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: build rustpython
|
- name: build rustpython
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
|
|
||||||
- name: collect tests data
|
- name: collect tests data
|
||||||
run: cargo run --release extra_tests/jsontests.py
|
run: cargo run --release extra_tests/jsontests.py
|
||||||
env:
|
env:
|
||||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
||||||
|
|
||||||
- name: upload tests data to the website
|
- name: upload tests data to the website
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
env:
|
env:
|
||||||
@@ -96,17 +109,19 @@ jobs:
|
|||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: actions/setup-python@v6.2.0
|
|
||||||
with:
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
- name: build rustpython
|
- name: build rustpython
|
||||||
run: cargo build --release --verbose
|
run: cargo build --release --verbose
|
||||||
|
|
||||||
- name: Collect what is left data
|
- name: Collect what is left data
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./scripts/whats_left.py
|
chmod +x ./scripts/whats_left.py
|
||||||
./scripts/whats_left.py --features "ssl,sqlite" > whats_left.temp
|
./scripts/whats_left.py --features "ssl,sqlite" > whats_left.temp
|
||||||
env:
|
env:
|
||||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
||||||
|
|
||||||
- name: Upload data to the website
|
- name: Upload data to the website
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
env:
|
env:
|
||||||
@@ -157,16 +172,20 @@ jobs:
|
|||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: actions/setup-python@v6.2.0
|
|
||||||
with:
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
- run: cargo install cargo-criterion
|
- run: cargo install cargo-criterion
|
||||||
|
|
||||||
- name: build benchmarks
|
- name: build benchmarks
|
||||||
run: cargo build --release --benches
|
run: cargo build --release --benches
|
||||||
|
|
||||||
- name: collect execution benchmark data
|
- name: collect execution benchmark data
|
||||||
run: cargo criterion --bench execution
|
run: cargo criterion --bench execution
|
||||||
|
|
||||||
- name: collect microbenchmarks data
|
- name: collect microbenchmarks data
|
||||||
run: cargo criterion --bench microbenchmarks
|
run: cargo criterion --bench microbenchmarks
|
||||||
|
|
||||||
- name: restructure generated files
|
- name: restructure generated files
|
||||||
run: |
|
run: |
|
||||||
cd ./target/criterion/reports
|
cd ./target/criterion/reports
|
||||||
@@ -179,6 +198,7 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
mv reports/* .
|
mv reports/* .
|
||||||
rmdir reports
|
rmdir reports
|
||||||
|
|
||||||
- name: upload benchmark data to the website
|
- name: upload benchmark data to the website
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
88
.github/workflows/lib-deps-check.yaml
vendored
88
.github/workflows/lib-deps-check.yaml
vendored
@@ -10,9 +10,6 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.14.3"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_deps:
|
check_deps:
|
||||||
permissions:
|
permissions:
|
||||||
@@ -37,46 +34,71 @@ jobs:
|
|||||||
# Checkout only Lib/ directory from PR head for accurate comparison
|
# Checkout only Lib/ directory from PR head for accurate comparison
|
||||||
git checkout ${{ github.event.pull_request.head.sha }} -- Lib/
|
git checkout ${{ github.event.pull_request.head.sha }} -- Lib/
|
||||||
|
|
||||||
- name: Checkout CPython
|
- name: Get target CPython version
|
||||||
|
id: cpython-version
|
||||||
run: |
|
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
|
- name: Get changed Lib files
|
||||||
id: changed-files
|
id: all-changed-files
|
||||||
run: |
|
run: |
|
||||||
# Get the list of changed files under Lib/
|
# 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<<EOF'
|
||||||
echo "$changed"
|
|
||||||
|
|
||||||
# Extract unique module names
|
git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py'
|
||||||
modules=""
|
|
||||||
for file in $changed; do
|
echo 'EOF'
|
||||||
if [[ "$file" == Lib/test/* ]]; then
|
} >> "$GITHUB_OUTPUT"
|
||||||
# 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|/.*||')
|
- name: Parse changed files
|
||||||
# Skip non-test files in test/ (e.g., support.py, __init__.py)
|
id: changed-files
|
||||||
if [[ ! "$module" == test_* ]]; then
|
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
|
continue
|
||||||
fi
|
else:
|
||||||
else
|
# Lib files:
|
||||||
# Lib files: Lib/foo.py -> foo, Lib/foo/__init__.py -> foo
|
# Lib/foo.py -> foo
|
||||||
module=$(echo "$file" | sed -E 's|^Lib/||; s|/__init__\.py$||; s|\.py$||; s|/.*||')
|
# Lib/foo/__init__.py -> foo
|
||||||
fi
|
module = file.removeprefix("Lib/").split("/")[0]
|
||||||
if [[ -n "$module" && ! " $modules " =~ " $module " ]]; then
|
|
||||||
modules="$modules $module"
|
module = module.split(".")[0]
|
||||||
fi
|
modules.add(module)
|
||||||
done
|
|
||||||
|
|
||||||
modules=$(echo "$modules" | xargs) # trim whitespace
|
print(f"{modules=}")
|
||||||
echo "Detected modules: $modules"
|
output = " ".join(sorted(modules))
|
||||||
echo "modules=$modules" >> $GITHUB_OUTPUT
|
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
|
- name: Setup Python
|
||||||
if: steps.changed-files.outputs.modules != ''
|
if: steps.changed-files.outputs.modules != ''
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
|
||||||
python-version: "${{ env.PYTHON_VERSION }}"
|
|
||||||
|
|
||||||
- name: Run deps check
|
- name: Run deps check
|
||||||
if: steps.changed-files.outputs.modules != ''
|
if: steps.changed-files.outputs.modules != ''
|
||||||
@@ -92,7 +114,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Post comment
|
- name: Post comment
|
||||||
if: steps.deps-check.outputs.deps_output != ''
|
if: steps.deps-check.outputs.deps_output != ''
|
||||||
uses: marocchino/sticky-pull-request-comment@v3
|
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
||||||
with:
|
with:
|
||||||
header: lib-deps-check
|
header: lib-deps-check
|
||||||
number: ${{ github.event.pull_request.number }}
|
number: ${{ github.event.pull_request.number }}
|
||||||
@@ -109,7 +131,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Remove comment if no Lib changes
|
- name: Remove comment if no Lib changes
|
||||||
if: steps.changed-files.outputs.modules == ''
|
if: steps.changed-files.outputs.modules == ''
|
||||||
uses: marocchino/sticky-pull-request-comment@v3
|
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
|
||||||
with:
|
with:
|
||||||
header: lib-deps-check
|
header: lib-deps-check
|
||||||
number: ${{ github.event.pull_request.number }}
|
number: ${{ github.event.pull_request.number }}
|
||||||
|
|||||||
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -16,11 +16,15 @@ env:
|
|||||||
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
|
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
|
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# Disable this scheduled job when running on a fork.
|
# Disable this scheduled job when running on a fork.
|
||||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@@ -64,7 +68,7 @@ jobs:
|
|||||||
libtool: true
|
libtool: true
|
||||||
|
|
||||||
- name: Build RustPython
|
- name: Build RustPython
|
||||||
run: cargo build --release --target=${{ matrix.target }} --verbose --no-default-features --features stdlib,stdio,importlib,encodings,sqlite,host_env,ssl-rustls,threading,jit
|
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
|
- name: Rename Binary
|
||||||
run: cp target/${{ matrix.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
run: cp target/${{ matrix.target }}/release/rustpython target/rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
||||||
@@ -75,7 +79,7 @@ jobs:
|
|||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
|
|
||||||
- name: Upload Binary Artifacts
|
- name: Upload Binary Artifacts
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
name: rustpython-release-${{ runner.os }}-${{ matrix.target }}
|
||||||
path: target/rustpython-release-${{ runner.os }}-${{ matrix.target }}*
|
path: target/rustpython-release-${{ runner.os }}-${{ matrix.target }}*
|
||||||
@@ -84,6 +88,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Disable this scheduled job when running on a fork.
|
# Disable this scheduled job when running on a fork.
|
||||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
@@ -100,7 +106,7 @@ jobs:
|
|||||||
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
|
run: cp target/wasm32-wasip1/release/rustpython.wasm target/rustpython-release-wasm32-wasip1.wasm
|
||||||
|
|
||||||
- name: Upload Binary Artifacts
|
- name: Upload Binary Artifacts
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: rustpython-release-wasm32-wasip1
|
name: rustpython-release-wasm32-wasip1
|
||||||
path: target/rustpython-release-wasm32-wasip1.wasm
|
path: target/rustpython-release-wasm32-wasip1.wasm
|
||||||
@@ -108,11 +114,11 @@ jobs:
|
|||||||
- name: install wasm-pack
|
- name: install wasm-pack
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
|
|
||||||
- uses: mwilliamson/setup-wabt-action@febe2a12b7ccb999a6e5d953a8362a3b7ffcf148 # v3.2.0
|
- uses: mwilliamson/setup-wabt-action@427f2fdd70bc4dbc2e53c2eb4f19f66162d71bd2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
wabt-version: "1.0.30"
|
wabt-version: "1.0.30"
|
||||||
|
|
||||||
@@ -135,7 +141,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy demo to Github Pages
|
- name: Deploy demo to Github Pages
|
||||||
if: ${{ github.repository == 'RustPython/RustPython' }}
|
if: ${{ github.repository == 'RustPython/RustPython' }}
|
||||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
|
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||||
with:
|
with:
|
||||||
deploy_key: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
deploy_key: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
||||||
publish_dir: ./wasm/demo/dist
|
publish_dir: ./wasm/demo/dist
|
||||||
@@ -147,6 +153,8 @@ jobs:
|
|||||||
# Disable this scheduled job when running on a fork.
|
# Disable this scheduled job when running on a fork.
|
||||||
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
if: ${{ github.repository == 'RustPython/RustPython' || github.event_name != 'schedule' }}
|
||||||
needs: [build, build-wasm]
|
needs: [build, build-wasm]
|
||||||
|
permissions:
|
||||||
|
contents: write # for creating a release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
@@ -187,7 +195,7 @@ jobs:
|
|||||||
$PRERELEASE_ARG \
|
$PRERELEASE_ARG \
|
||||||
bin/rustpython-release-*
|
bin/rustpython-release-*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
tag: ${{ github.ref_name }}
|
tag: ${{ github.ref_name }}
|
||||||
run: ${{ github.run_number }}
|
run: ${{ github.run_number }}
|
||||||
PRE_RELEASE_INPUT: ${{ github.event.inputs.pre-release }}
|
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 }}
|
||||||
25
.github/workflows/update-doc-db.yml
vendored
25
.github/workflows/update-doc-db.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Generate docs
|
- name: Generate docs
|
||||||
run: python crates/doc/generate.py
|
run: python crates/doc/generate.py
|
||||||
|
|
||||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: doc-db-${{ inputs.python-version }}-${{ matrix.os }}
|
name: doc-db-${{ inputs.python-version }}-${{ matrix.os }}
|
||||||
path: "crates/doc/generated/*.json"
|
path: "crates/doc/generated/*.json"
|
||||||
@@ -87,19 +87,18 @@ jobs:
|
|||||||
|
|
||||||
OUTPUT_FILE='crates/doc/src/data.inc.rs'
|
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
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
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
|
|
||||||
with:
|
with:
|
||||||
name: doc-db-${{ inputs.python-version }}
|
name: doc-db-${{ inputs.python-version }}
|
||||||
path: "crates/doc/src/data.inc.rs"
|
path: "crates/doc/src/data.inc.rs"
|
||||||
|
|||||||
16
.github/workflows/update-libs-status.yaml
vendored
16
.github/workflows/update-libs-status.yaml
vendored
@@ -13,7 +13,6 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "v3.14.3"
|
|
||||||
ISSUE_ID: "6839"
|
ISSUE_ID: "6839"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -29,13 +28,20 @@ jobs:
|
|||||||
sparse-checkout: |-
|
sparse-checkout: |-
|
||||||
Lib
|
Lib
|
||||||
scripts/update_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
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
repository: python/cpython
|
repository: python/cpython
|
||||||
path: cpython
|
path: cpython
|
||||||
ref: ${{ env.PYTHON_VERSION }}
|
ref: "v${{ steps.cpython-version.outputs.version }}"
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
Lib
|
Lib
|
||||||
@@ -56,14 +62,14 @@ jobs:
|
|||||||
|
|
||||||
## Summary
|
## 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
|
Previous versions' issues as reference
|
||||||
- 3.13: #5529
|
- 3.13: #5529
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Quick guideline for Copilot:
|
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.
|
# Pick a library or test to update. Probably user give one.
|
||||||
# Run \`python3 scripts/update_lib quick <name>\`
|
# Run \`python3 scripts/update_lib quick <name>\`
|
||||||
# A commit is automatically created. push the commit.
|
# 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: ""
|
comment_repo: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Scripts
|
- name: Setup Scripts
|
||||||
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
|
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||||
with:
|
with:
|
||||||
destination: /opt/gh-aw/actions
|
destination: /opt/gh-aw/actions
|
||||||
- name: Check workflow file timestamps
|
- name: Check workflow file timestamps
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_WORKFLOW_FILE: "upgrade-pylib.lock.yml"
|
GH_AW_WORKFLOW_FILE: "upgrade-pylib.lock.yml"
|
||||||
with:
|
with:
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
|
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Scripts
|
- name: Setup Scripts
|
||||||
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
|
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||||
with:
|
with:
|
||||||
destination: /opt/gh-aw/actions
|
destination: /opt/gh-aw/actions
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
|
run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
|
||||||
# Cache configuration from frontmatter processed below
|
# Cache configuration from frontmatter processed below
|
||||||
- name: Cache (cpython-lib-${{ env.PYTHON_VERSION }})
|
- name: Cache (cpython-lib-${{ env.PYTHON_VERSION }})
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
key: cpython-lib-${{ env.PYTHON_VERSION }}
|
key: cpython-lib-${{ env.PYTHON_VERSION }}
|
||||||
path: cpython
|
path: cpython
|
||||||
@@ -135,7 +135,7 @@ jobs:
|
|||||||
id: checkout-pr
|
id: checkout-pr
|
||||||
if: |
|
if: |
|
||||||
github.event.pull_request
|
github.event.pull_request
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@@ -147,7 +147,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Generate agentic run info
|
- name: Generate agentic run info
|
||||||
id: generate_aw_info
|
id: generate_aw_info
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -201,7 +201,7 @@ jobs:
|
|||||||
run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.16.4
|
run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.16.4
|
||||||
- name: Determine automatic lockdown mode for GitHub MCP server
|
- name: Determine automatic lockdown mode for GitHub MCP server
|
||||||
id: determine-automatic-lockdown
|
id: determine-automatic-lockdown
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
||||||
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
||||||
@@ -484,7 +484,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
GH_AW_MCP_CONFIG_EOF
|
GH_AW_MCP_CONFIG_EOF
|
||||||
- name: Generate workflow overview
|
- name: Generate workflow overview
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
|
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
|
||||||
@@ -508,10 +508,11 @@ jobs:
|
|||||||
cat << 'GH_AW_PROMPT_EOF' > "$GH_AW_PROMPT"
|
cat << 'GH_AW_PROMPT_EOF' > "$GH_AW_PROMPT"
|
||||||
<system>
|
<system>
|
||||||
GH_AW_PROMPT_EOF
|
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/xpia.md"
|
||||||
cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
|
cat "/opt/gh-aw/prompts/temp_folder_prompt.md"
|
||||||
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
|
cat "/opt/gh-aw/prompts/markdown.md"
|
||||||
|
cat << 'GH_AW_PROMPT_EOF'
|
||||||
<safe-outputs>
|
<safe-outputs>
|
||||||
<description>GitHub API Access Instructions</description>
|
<description>GitHub API Access Instructions</description>
|
||||||
<important>
|
<important>
|
||||||
@@ -569,14 +570,15 @@ jobs:
|
|||||||
</github-context>
|
</github-context>
|
||||||
|
|
||||||
GH_AW_PROMPT_EOF
|
GH_AW_PROMPT_EOF
|
||||||
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
|
cat << 'GH_AW_PROMPT_EOF'
|
||||||
</system>
|
</system>
|
||||||
GH_AW_PROMPT_EOF
|
GH_AW_PROMPT_EOF
|
||||||
cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
|
cat << 'GH_AW_PROMPT_EOF'
|
||||||
{{#runtime-import .github/workflows/upgrade-pylib.md}}
|
{{#runtime-import .github/workflows/upgrade-pylib.md}}
|
||||||
GH_AW_PROMPT_EOF
|
GH_AW_PROMPT_EOF
|
||||||
|
} >> "$GH_AW_PROMPT"
|
||||||
- name: Substitute placeholders
|
- name: Substitute placeholders
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||||
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
|
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
|
||||||
@@ -610,7 +612,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
- name: Interpolate variables and render templates
|
- name: Interpolate variables and render templates
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||||
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
|
GH_AW_ENV_ISSUE_ID: ${{ env.ISSUE_ID }}
|
||||||
@@ -690,7 +692,7 @@ jobs:
|
|||||||
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
|
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
|
||||||
- name: Redact secrets in logs
|
- name: Redact secrets in logs
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||||
@@ -705,14 +707,14 @@ jobs:
|
|||||||
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Upload Safe Outputs
|
- name: Upload Safe Outputs
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: safe-output
|
name: safe-output
|
||||||
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
- name: Ingest agent output
|
- name: Ingest agent output
|
||||||
id: collect_output
|
id: collect_output
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
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"
|
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();
|
await main();
|
||||||
- name: Upload sanitized agent output
|
- name: Upload sanitized agent output
|
||||||
if: always() && env.GH_AW_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:
|
with:
|
||||||
name: agent-output
|
name: agent-output
|
||||||
path: ${{ env.GH_AW_AGENT_OUTPUT }}
|
path: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
- name: Upload engine output files
|
- name: Upload engine output files
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: agent_outputs
|
name: agent_outputs
|
||||||
path: |
|
path: |
|
||||||
@@ -741,7 +743,7 @@ jobs:
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
- name: Parse agent logs for step summary
|
- name: Parse agent logs for step summary
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
|
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
|
||||||
with:
|
with:
|
||||||
@@ -752,7 +754,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Parse MCP gateway logs for step summary
|
- name: Parse MCP gateway logs for step summary
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||||
@@ -772,7 +774,7 @@ jobs:
|
|||||||
- name: Upload agent artifacts
|
- name: Upload agent artifacts
|
||||||
if: always()
|
if: always()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: agent-artifacts
|
name: agent-artifacts
|
||||||
path: |
|
path: |
|
||||||
@@ -804,7 +806,7 @@ jobs:
|
|||||||
total_count: ${{ steps.missing_tool.outputs.total_count }}
|
total_count: ${{ steps.missing_tool.outputs.total_count }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Scripts
|
- name: Setup Scripts
|
||||||
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
|
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||||
with:
|
with:
|
||||||
destination: /opt/gh-aw/actions
|
destination: /opt/gh-aw/actions
|
||||||
- name: Download agent output artifact
|
- name: Download agent output artifact
|
||||||
@@ -820,7 +822,7 @@ jobs:
|
|||||||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
|
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
|
||||||
- name: Process No-Op Messages
|
- name: Process No-Op Messages
|
||||||
id: noop
|
id: noop
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
GH_AW_NOOP_MAX: 1
|
GH_AW_NOOP_MAX: 1
|
||||||
@@ -834,7 +836,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Record Missing Tool
|
- name: Record Missing Tool
|
||||||
id: missing_tool
|
id: missing_tool
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||||
@@ -847,7 +849,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Handle Agent Failure
|
- name: Handle Agent Failure
|
||||||
id: handle_agent_failure
|
id: handle_agent_failure
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||||
@@ -865,7 +867,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Handle No-Op Message
|
- name: Handle No-Op Message
|
||||||
id: handle_noop_message
|
id: handle_noop_message
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||||
@@ -882,7 +884,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Handle Create Pull Request Error
|
- name: Handle Create Pull Request Error
|
||||||
id: handle_create_pr_error
|
id: handle_create_pr_error
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
GH_AW_WORKFLOW_NAME: "Upgrade Python Library"
|
||||||
@@ -896,7 +898,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Update reaction comment with completion status
|
- name: Update reaction comment with completion status
|
||||||
id: conclusion
|
id: conclusion
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||||
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
|
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
|
||||||
@@ -925,7 +927,7 @@ jobs:
|
|||||||
success: ${{ steps.parse_results.outputs.success }}
|
success: ${{ steps.parse_results.outputs.success }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Scripts
|
- name: Setup Scripts
|
||||||
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
|
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||||
with:
|
with:
|
||||||
destination: /opt/gh-aw/actions
|
destination: /opt/gh-aw/actions
|
||||||
- name: Download agent artifacts
|
- name: Download agent artifacts
|
||||||
@@ -946,7 +948,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
|
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
|
||||||
- name: Setup threat detection
|
- name: Setup threat detection
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
WORKFLOW_NAME: "Upgrade Python Library"
|
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."
|
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
|
XDG_CONFIG_HOME: /home/runner
|
||||||
- name: Parse threat detection results
|
- name: Parse threat detection results
|
||||||
id: parse_results
|
id: parse_results
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||||
@@ -1008,7 +1010,7 @@ jobs:
|
|||||||
await main();
|
await main();
|
||||||
- name: Upload threat detection log
|
- name: Upload threat detection log
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: threat-detection.log
|
name: threat-detection.log
|
||||||
path: /tmp/gh-aw/threat-detection/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 }}
|
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Scripts
|
- name: Setup Scripts
|
||||||
uses: github/gh-aw/actions/setup@48d8fdfddc8cad854ac0c70ceb573f09fb8f9c9b # v0.62.5
|
uses: github/gh-aw/actions/setup@2c1a237d2048b0e2412e7d7528892ea1257840e2 # v0.74.4
|
||||||
with:
|
with:
|
||||||
destination: /opt/gh-aw/actions
|
destination: /opt/gh-aw/actions
|
||||||
- name: Download agent output artifact
|
- name: Download agent output artifact
|
||||||
@@ -1079,7 +1081,7 @@ jobs:
|
|||||||
echo "Git configured with standard GitHub Actions identity"
|
echo "Git configured with standard GitHub Actions identity"
|
||||||
- name: Process Safe Outputs
|
- name: Process Safe Outputs
|
||||||
id: process_safe_outputs
|
id: process_safe_outputs
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
env:
|
env:
|
||||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
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\":{}}"
|
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-
|
- cpython-lib-
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "v3.14.3"
|
PYTHON_VERSION: "v3.14.4"
|
||||||
ISSUE_ID: "6839"
|
ISSUE_ID: "6839"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,7 +10,6 @@ __pycache__/
|
|||||||
wasm-pack.log
|
wasm-pack.log
|
||||||
.idea/
|
.idea/
|
||||||
.envrc
|
.envrc
|
||||||
.python-version
|
|
||||||
|
|
||||||
flame-graph.html
|
flame-graph.html
|
||||||
flame.txt
|
flame.txt
|
||||||
@@ -28,4 +27,4 @@ Lib/site-packages/*
|
|||||||
Lib/test/data/*
|
Lib/test/data/*
|
||||||
!Lib/test/data/README
|
!Lib/test/data/README
|
||||||
cpython/
|
cpython/
|
||||||
|
.claude/scheduled_tasks.lock
|
||||||
@@ -10,7 +10,7 @@ repos:
|
|||||||
priority: 0
|
priority: 0
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.15.7
|
rev: v0.15.12
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
priority: 0
|
priority: 0
|
||||||
@@ -40,17 +40,30 @@ repos:
|
|||||||
types: [rust]
|
types: [rust]
|
||||||
priority: 0
|
priority: 0
|
||||||
|
|
||||||
- id: generate-opcode-metadata
|
- id: generate-rs-opcode-metadata
|
||||||
name: generate opcode metadata
|
name: generate rust opcode metadata
|
||||||
entry: python scripts/generate_opcode_metadata.py
|
entry: python tools/opcode_metadata/generate_rs_opcode_metadata.py
|
||||||
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|scripts/generate_opcode_metadata\.py)$'
|
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
language: system
|
language: system
|
||||||
require_serial: true
|
require_serial: true
|
||||||
priority: 1 # so rustfmt runs first
|
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
|
- repo: https://github.com/streetsidesoftware/cspell-cli
|
||||||
rev: v9.7.0
|
rev: v10.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: cspell
|
- id: cspell
|
||||||
types: [rust]
|
types: [rust]
|
||||||
@@ -64,7 +77,7 @@ repos:
|
|||||||
priority: 0
|
priority: 0
|
||||||
|
|
||||||
- repo: https://github.com/rbubley/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: v3.8.1
|
rev: v3.8.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
files: '^wasm/.*$'
|
files: '^wasm/.*$'
|
||||||
|
|||||||
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
|
- 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
|
- 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
|
## Important Development Notes
|
||||||
|
|
||||||
### Running Python Code
|
### 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.skip("TODO: RustPython <reason>")`
|
||||||
- `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment
|
- `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
|
### Clean Build
|
||||||
|
|
||||||
When you modify bytecode instructions, a full clean is required:
|
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 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 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
|
#### Avoid Duplicate Code in Branches
|
||||||
|
|
||||||
@@ -258,9 +294,14 @@ See DEVELOPMENT.md "CPython Version Upgrade Checklist" section.
|
|||||||
- Document that it requires PEP 695 support
|
- Document that it requires PEP 695 support
|
||||||
- Focus on tests that can be fixed through Rust code changes only
|
- 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
|
## Documentation
|
||||||
|
|
||||||
- Check the [architecture document](/architecture/architecture.md) for a high-level overview
|
- Check the [architecture document](/architecture/architecture.md) for a high-level overview
|
||||||
- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions
|
- Read the [development guide](/DEVELOPMENT.md) for detailed setup instructions
|
||||||
- Generate documentation with `cargo doc --no-deps --all`
|
- Generate documentation with `cargo doc --no-deps --all`
|
||||||
- Online documentation is available at [docs.rs/rustpython](https://docs.rs/rustpython/)
|
- 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,
|
RustPython attracts developers with interest and experience in Rust, Python,
|
||||||
or WebAssembly. Whether you are familiar with Rust, Python, or
|
or WebAssembly. Whether you are familiar with Rust, Python, or
|
||||||
1688
Cargo.lock
generated
1688
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
188
Cargo.toml
188
Cargo.toml
@@ -10,7 +10,8 @@ repository.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[features]
|
[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"]
|
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
|
||||||
importlib = ["rustpython-vm/importlib"]
|
importlib = ["rustpython-vm/importlib"]
|
||||||
encodings = ["rustpython-vm/encodings"]
|
encodings = ["rustpython-vm/encodings"]
|
||||||
@@ -21,30 +22,35 @@ freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/fre
|
|||||||
jit = ["rustpython-vm/jit"]
|
jit = ["rustpython-vm/jit"]
|
||||||
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"]
|
||||||
sqlite = ["rustpython-stdlib/sqlite"]
|
sqlite = ["rustpython-stdlib/sqlite"]
|
||||||
ssl = []
|
ssl = ["host_env"]
|
||||||
ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"]
|
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-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"]
|
tkinter = ["rustpython-stdlib/tkinter"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
winresource = "0.1"
|
winresource = "0.1"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rustpython-capi = { workspace = true, optional = true }
|
||||||
rustpython-compiler = { workspace = true }
|
rustpython-compiler = { workspace = true }
|
||||||
rustpython-pylib = { workspace = true, optional = true }
|
rustpython-pylib = { workspace = true, optional = true }
|
||||||
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
|
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
|
||||||
rustpython-vm = { workspace = true, features = ["compiler", "gc"] }
|
rustpython-vm = { workspace = true, features = ["compiler", "gc"] }
|
||||||
|
|
||||||
cfg-if = { workspace = true }
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
flame = { workspace = true, optional = true }
|
flame = { workspace = true, optional = true }
|
||||||
|
|
||||||
lexopt = "0.3"
|
lexopt = "0.3"
|
||||||
dirs = { package = "dirs-next", version = "2.0" }
|
dirs = "6"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
flamescope = { version = "0.1.2", optional = true }
|
flamescope = { version = "0.1.2", optional = true }
|
||||||
|
|
||||||
|
rustls = { workspace = true, optional = true }
|
||||||
|
rustls-graviola = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
|
||||||
@@ -53,7 +59,7 @@ rustyline = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true }
|
criterion = { workspace = true }
|
||||||
pyo3 = { version = "0.28.2", features = ["auto-initialize"] }
|
pyo3 = { workspace = true, features = ["auto-initialize"] }
|
||||||
rustpython-stdlib = { workspace = true }
|
rustpython-stdlib = { workspace = true }
|
||||||
ruff_python_parser = { workspace = true }
|
ruff_python_parser = { workspace = true }
|
||||||
|
|
||||||
@@ -69,6 +75,17 @@ harness = false
|
|||||||
name = "rustpython"
|
name = "rustpython"
|
||||||
path = "src/main.rs"
|
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."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
@@ -136,15 +153,17 @@ exclude = ["pymath"]
|
|||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
authors = ["RustPython Team"]
|
authors = ["RustPython Team"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.93.0"
|
rust-version = "1.95.0"
|
||||||
repository = "https://github.com/RustPython/RustPython"
|
repository = "https://github.com/RustPython/RustPython"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
rustpython-capi = { path = "crates/capi", version = "0.5.0" }
|
||||||
rustpython-compiler-core = { path = "crates/compiler-core", 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-compiler = { path = "crates/compiler", version = "0.5.0" }
|
||||||
rustpython-codegen = { path = "crates/codegen", version = "0.5.0" }
|
rustpython-codegen = { path = "crates/codegen", version = "0.5.0" }
|
||||||
rustpython-common = { path = "crates/common", 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 = { path = "crates/derive", version = "0.5.0" }
|
||||||
rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" }
|
rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" }
|
||||||
rustpython-jit = { path = "crates/jit", version = "0.5.0" }
|
rustpython-jit = { path = "crates/jit", version = "0.5.0" }
|
||||||
@@ -170,66 +189,141 @@ ruff_source_file = { package = "rustpython-ruff_source_file", version = "0.15.8"
|
|||||||
# ruff_text_size = { 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" }
|
# 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"]}
|
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"
|
ascii = "1.1"
|
||||||
|
base64 = "0.22"
|
||||||
|
blake2 = "0.10.4"
|
||||||
bitflags = "2.11.0"
|
bitflags = "2.11.0"
|
||||||
bitflagset = "0.0.3"
|
bitflagset = "0.0.3"
|
||||||
bstr = "1"
|
bstr = "1"
|
||||||
cfg-if = "1.0"
|
bzip2 = "0.6"
|
||||||
chrono = { version = "0.4.44", default-features = false, features = ["clock", "oldtime", "std"] }
|
chrono = { version = "0.4.44", default-features = false, features = ["clock", "std"] }
|
||||||
|
console_error_panic_hook = "0.1"
|
||||||
constant_time_eq = "0.4"
|
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"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
crossbeam-utils = "0.8.21"
|
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"
|
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"] }
|
getrandom = { version = "0.3", features = ["std"] }
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
half = "2"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
indexmap = { version = "2.13.0", features = ["std"] }
|
hexf-parse = "0.2.1"
|
||||||
insta = "1.46"
|
hmac = "0.12"
|
||||||
|
indexmap = { version = "2.14.0", features = ["std"] }
|
||||||
|
insta = "1.47"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
is-macro = "0.3.7"
|
is-macro = "0.3.7"
|
||||||
|
js-sys = "0.3"
|
||||||
junction = "1.4.2"
|
junction = "1.4.2"
|
||||||
libc = "0.2.183"
|
lexical-parse-float = "1.0.6"
|
||||||
|
libc = "0.2.186"
|
||||||
libffi = "5"
|
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"
|
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-bigint = "0.9.1"
|
||||||
malachite-q = "0.9.1"
|
malachite-q = "0.9.1"
|
||||||
malachite-base = "0.9.1"
|
malachite-base = "0.9.1"
|
||||||
|
md-5 = "0.10.1"
|
||||||
memchr = "2.8.0"
|
memchr = "2.8.0"
|
||||||
|
memmap2 = "0.9.10"
|
||||||
|
mt19937 = "<=3.2" # upgrade it once rand is upgraded
|
||||||
num-complex = "0.4.6"
|
num-complex = "0.4.6"
|
||||||
num-integer = "0.1.46"
|
num-integer = "0.1.46"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
num_cpus = "1.17.0"
|
||||||
num_enum = { version = "0.7", default-features = false }
|
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"
|
optional = "0.5"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
|
pbkdf2 = "0.12"
|
||||||
|
pem-rfc7468 = "1.0"
|
||||||
|
pkcs8 = "0.11"
|
||||||
proc-macro2 = "1.0.105"
|
proc-macro2 = "1.0.105"
|
||||||
|
psm = "0.1"
|
||||||
pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
|
pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
|
||||||
|
pyo3 = "0.28"
|
||||||
quote = "1.0.45"
|
quote = "1.0.45"
|
||||||
radium = "1.1.1"
|
radium = "1.1.1"
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
rand_core = { version = "0.9", features = ["os_rng"] }
|
rand_core = { version = "0.9", features = ["os_rng"] }
|
||||||
rustix = { version = "1.1", features = ["event"] }
|
rapidhash = "4.4.1"
|
||||||
rustyline = "17.0.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"] }
|
serde = { package = "serde_core", version = "1.0.225", default-features = false, features = ["alloc"] }
|
||||||
schannel = "0.1.29"
|
schannel = "0.1.29"
|
||||||
scoped-tls = "1"
|
|
||||||
scopeguard = "1"
|
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"
|
static_assertions = "1.1"
|
||||||
strum = "0.28"
|
strum = "0.28"
|
||||||
strum_macros = "0.28"
|
strum_macros = "0.28"
|
||||||
syn = "2"
|
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"
|
thiserror = "2.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_properties = "2"
|
||||||
icu_normalizer = "2"
|
icu_normalizer = "2"
|
||||||
unicode-casing = "0.1.1"
|
uuid = "1.23.1"
|
||||||
|
ucd = "0.1.1"
|
||||||
unic-ucd-age = "0.9.0"
|
unic-ucd-age = "0.9.0"
|
||||||
unicode_names2 = "2.0.0"
|
unicode_names2 = "2.0.0"
|
||||||
widestring = "1.2.0"
|
widestring = "1.2.0"
|
||||||
windows-sys = "0.61.2"
|
windows-sys = "0.61.2"
|
||||||
wasm-bindgen = "0.2.106"
|
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
|
# Lints
|
||||||
|
|
||||||
@@ -237,13 +331,61 @@ wasm-bindgen = "0.2.106"
|
|||||||
unsafe_code = "allow"
|
unsafe_code = "allow"
|
||||||
unsafe_op_in_unsafe_fn = "deny"
|
unsafe_op_in_unsafe_fn = "deny"
|
||||||
elided_lifetimes_in_paths = "warn"
|
elided_lifetimes_in_paths = "warn"
|
||||||
|
unreachable_pub = "warn"
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[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"
|
alloc_instead_of_core = "warn"
|
||||||
|
cfg_not_test = "warn"
|
||||||
|
redundant_test_prefix = "warn"
|
||||||
std_instead_of_alloc = "warn"
|
std_instead_of_alloc = "warn"
|
||||||
std_instead_of_core = "warn"
|
std_instead_of_core = "warn"
|
||||||
perf = "warn"
|
tests_outside_test_module = "warn"
|
||||||
style = "warn"
|
|
||||||
complexity = "warn"
|
# nursery lints to enforce gradually
|
||||||
suspicious = "warn"
|
debug_assert_with_mut_call = "warn"
|
||||||
correctness = "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
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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 = message.replace(b"\x00", b"\xc0\x80")
|
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:
|
with self._lock:
|
||||||
now = time()
|
now = time()
|
||||||
self._bucket_level += (
|
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).
|
# for RustPython bytecode format (CPython 3.14 compatible opcode numbers).
|
||||||
# Do not edit!
|
# Do not edit!
|
||||||
|
|
||||||
|
|||||||
55
Lib/annotationlib.py
vendored
55
Lib/annotationlib.py
vendored
@@ -47,6 +47,7 @@ _SLOTS = (
|
|||||||
"__cell__",
|
"__cell__",
|
||||||
"__owner__",
|
"__owner__",
|
||||||
"__stringifier_dict__",
|
"__stringifier_dict__",
|
||||||
|
"__resolved_str_cache__",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ class ForwardRef:
|
|||||||
# value later.
|
# value later.
|
||||||
self.__code__ = None
|
self.__code__ = None
|
||||||
self.__ast_node__ = None
|
self.__ast_node__ = None
|
||||||
|
self.__resolved_str_cache__ = None
|
||||||
|
|
||||||
def __init_subclass__(cls, /, *args, **kwds):
|
def __init_subclass__(cls, /, *args, **kwds):
|
||||||
raise TypeError("Cannot subclass ForwardRef")
|
raise TypeError("Cannot subclass ForwardRef")
|
||||||
@@ -113,7 +115,7 @@ class ForwardRef:
|
|||||||
"""
|
"""
|
||||||
match format:
|
match format:
|
||||||
case Format.STRING:
|
case Format.STRING:
|
||||||
return self.__forward_arg__
|
return self.__resolved_str__
|
||||||
case Format.VALUE:
|
case Format.VALUE:
|
||||||
is_forwardref_format = False
|
is_forwardref_format = False
|
||||||
case Format.FORWARDREF:
|
case Format.FORWARDREF:
|
||||||
@@ -258,6 +260,24 @@ class ForwardRef:
|
|||||||
"Attempted to access '__forward_arg__' on an uninitialized 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
|
@property
|
||||||
def __forward_code__(self):
|
def __forward_code__(self):
|
||||||
if self.__code__ is not None:
|
if self.__code__ is not None:
|
||||||
@@ -321,7 +341,7 @@ class ForwardRef:
|
|||||||
extra.append(", is_class=True")
|
extra.append(", is_class=True")
|
||||||
if self.__owner__ is not None:
|
if self.__owner__ is not None:
|
||||||
extra.append(f", owner={self.__owner__!r}")
|
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"")
|
_Template = type(t"")
|
||||||
@@ -357,6 +377,7 @@ class _Stringifier:
|
|||||||
self.__cell__ = cell
|
self.__cell__ = cell
|
||||||
self.__owner__ = owner
|
self.__owner__ = owner
|
||||||
self.__stringifier_dict__ = stringifier_dict
|
self.__stringifier_dict__ = stringifier_dict
|
||||||
|
self.__resolved_str_cache__ = None # Needed for ForwardRef
|
||||||
|
|
||||||
def __convert_to_ast(self, other):
|
def __convert_to_ast(self, other):
|
||||||
if isinstance(other, _Stringifier):
|
if isinstance(other, _Stringifier):
|
||||||
@@ -919,7 +940,7 @@ def get_annotations(
|
|||||||
does not exist, the __annotate__ function is called. The
|
does not exist, the __annotate__ function is called. The
|
||||||
FORWARDREF format uses __annotations__ if it exists and can be
|
FORWARDREF format uses __annotations__ if it exists and can be
|
||||||
evaluated, and otherwise falls back to calling the __annotate__ function.
|
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().
|
using __annotations__, stringified using annotations_to_string().
|
||||||
|
|
||||||
This function handles several details for you:
|
This function handles several details for you:
|
||||||
@@ -1037,13 +1058,26 @@ def get_annotations(
|
|||||||
obj_globals = obj_locals = unwrap = None
|
obj_globals = obj_locals = unwrap = None
|
||||||
|
|
||||||
if unwrap is not 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:
|
while True:
|
||||||
if hasattr(unwrap, "__wrapped__"):
|
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
|
continue
|
||||||
if functools := sys.modules.get("functools"):
|
if functools := sys.modules.get("functools"):
|
||||||
if isinstance(unwrap, functools.partial):
|
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
|
continue
|
||||||
break
|
break
|
||||||
if hasattr(unwrap, "__globals__"):
|
if hasattr(unwrap, "__globals__"):
|
||||||
@@ -1150,3 +1184,14 @@ def _get_dunder_annotations(obj):
|
|||||||
if not isinstance(ann, dict):
|
if not isinstance(ann, dict):
|
||||||
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
|
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
|
||||||
return ann
|
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)
|
return copy.copy(items)
|
||||||
|
|
||||||
|
|
||||||
|
def _identity(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
# ===============
|
# ===============
|
||||||
# Formatting Help
|
# Formatting Help
|
||||||
# ===============
|
# ===============
|
||||||
@@ -200,7 +204,7 @@ class HelpFormatter(object):
|
|||||||
self._decolor = decolor
|
self._decolor = decolor
|
||||||
else:
|
else:
|
||||||
self._theme = get_theme(force_no_color=True).argparse
|
self._theme = get_theme(force_no_color=True).argparse
|
||||||
self._decolor = lambda text: text
|
self._decolor = _identity
|
||||||
|
|
||||||
# ===============================
|
# ===============================
|
||||||
# Section and indentation methods
|
# Section and indentation methods
|
||||||
@@ -1903,9 +1907,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
self._subparsers = None
|
self._subparsers = None
|
||||||
|
|
||||||
# register types
|
# register types
|
||||||
def identity(string):
|
self.register('type', None, _identity)
|
||||||
return string
|
|
||||||
self.register('type', None, identity)
|
|
||||||
|
|
||||||
# add help argument if necessary
|
# add help argument if necessary
|
||||||
# (using explicit default to override global argument_default)
|
# (using explicit default to override global argument_default)
|
||||||
@@ -2676,7 +2678,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
|
|||||||
|
|
||||||
if value not in choices:
|
if value not in choices:
|
||||||
args = {'value': str(value),
|
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)')
|
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
|
||||||
|
|
||||||
if self.suggest_on_error and isinstance(value, str):
|
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()
|
val = self.default_factory()
|
||||||
else:
|
else:
|
||||||
raise KeyError(key)
|
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
|
self[key] = val
|
||||||
return 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):
|
def append(self, lineno, line):
|
||||||
self.errors.append((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):
|
def combine(self, others):
|
||||||
|
messages = [self.message]
|
||||||
for other in others:
|
for other in others:
|
||||||
for error in other.errors:
|
for lineno, line in other.errors:
|
||||||
self.append(*error)
|
self.errors.append((lineno, line))
|
||||||
|
messages.append(f'\n\t[line {lineno:2d}]: {line!r}')
|
||||||
|
self.message = "".join(messages)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -613,7 +616,9 @@ class RawConfigParser(MutableMapping):
|
|||||||
\] # ]
|
\] # ]
|
||||||
"""
|
"""
|
||||||
_OPT_TMPL = r"""
|
_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,
|
\s*(?P<vi>{delim})\s* # any number of space/tab,
|
||||||
# followed by any of the
|
# followed by any of the
|
||||||
# allowed delimiters,
|
# allowed delimiters,
|
||||||
@@ -621,7 +626,9 @@ class RawConfigParser(MutableMapping):
|
|||||||
(?P<value>.*)$ # everything up to eol
|
(?P<value>.*)$ # everything up to eol
|
||||||
"""
|
"""
|
||||||
_OPT_NV_TMPL = r"""
|
_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,
|
\s*(?: # any number of space/tab,
|
||||||
(?P<vi>{delim})\s* # optionally followed by
|
(?P<vi>{delim})\s* # optionally followed by
|
||||||
# any of the allowed
|
# 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:
|
if name and name.endswith(")") and ".a(" in name:
|
||||||
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
|
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
|
||||||
self._name = name
|
self._name = name
|
||||||
|
if handle is not None:
|
||||||
|
return handle
|
||||||
return _dlopen(name, mode)
|
return _dlopen(name, mode)
|
||||||
|
|
||||||
def __repr__(self):
|
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,
|
wintypes.DWORD,
|
||||||
)
|
)
|
||||||
|
|
||||||
_psapi = ctypes.WinDLL('psapi', use_last_error=True)
|
# gh-145307: We defer loading psapi.dll until _get_module_handles is called.
|
||||||
_enum_process_modules = _psapi["EnumProcessModules"]
|
# Loading additional DLLs at startup for functionality that may never be
|
||||||
_enum_process_modules.restype = wintypes.BOOL
|
# used is wasteful.
|
||||||
_enum_process_modules.argtypes = (
|
_enum_process_modules = None
|
||||||
wintypes.HANDLE,
|
|
||||||
ctypes.POINTER(wintypes.HMODULE),
|
|
||||||
wintypes.DWORD,
|
|
||||||
wintypes.LPDWORD,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_module_filename(module: wintypes.HMODULE):
|
def _get_module_filename(module: wintypes.HMODULE):
|
||||||
name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS
|
name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS
|
||||||
@@ -101,8 +96,19 @@ if os.name == "nt":
|
|||||||
return name.value
|
return name.value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_module_handles():
|
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()
|
process = _get_current_process()
|
||||||
space_needed = wintypes.DWORD()
|
space_needed = wintypes.DWORD()
|
||||||
n = 1024
|
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)
|
annotation_fields=annotation_fields)
|
||||||
|
|
||||||
|
|
||||||
def _frozen_get_del_attr(cls, fields, func_builder):
|
def _frozen_set_del_attr(cls, fields, func_builder):
|
||||||
locals = {'cls': cls,
|
locals = {'__class__': cls,
|
||||||
'FrozenInstanceError': FrozenInstanceError}
|
'FrozenInstanceError': FrozenInstanceError}
|
||||||
condition = 'type(self) is cls'
|
condition = 'type(self) is __class__'
|
||||||
if fields:
|
if fields:
|
||||||
condition += ' or name in {' + ', '.join(repr(f.name) for f in 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'),
|
('self', 'name', 'value'),
|
||||||
(f' if {condition}:',
|
(f' if {condition}:',
|
||||||
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
|
' 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,
|
locals=locals,
|
||||||
overwrite_error=True)
|
overwrite_error=True)
|
||||||
func_builder.add_fn('__delattr__',
|
func_builder.add_fn('__delattr__',
|
||||||
('self', 'name'),
|
('self', 'name'),
|
||||||
(f' if {condition}:',
|
(f' if {condition}:',
|
||||||
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
||||||
f' super(cls, self).__delattr__(name)'),
|
f' super(__class__, self).__delattr__(name)'),
|
||||||
locals=locals,
|
locals=locals,
|
||||||
overwrite_error=True)
|
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')
|
overwrite_error='Consider using functools.total_ordering')
|
||||||
|
|
||||||
if frozen:
|
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.
|
# Decide if/how we're going to create a hash function.
|
||||||
hash_action = _hash_action[bool(unsafe_hash),
|
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.
|
# This function doesn't reference __class__, so nothing to do.
|
||||||
return False
|
return False
|
||||||
# Fix the cell to point to the new class, if it's already pointing
|
# 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
|
# at the old class.
|
||||||
# is needed, but other than performance can't hurt.
|
|
||||||
closure = f.__closure__[idx]
|
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
|
closure.cell_contents = newcls
|
||||||
return True
|
return True
|
||||||
return False
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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':
|
if charset == 'unknown-8bit':
|
||||||
bstring = string.encode('ascii', 'surrogateescape')
|
bstring = string.encode('utf-8', 'surrogateescape')
|
||||||
else:
|
else:
|
||||||
bstring = string.encode(charset)
|
bstring = string.encode(charset)
|
||||||
if encoding is None:
|
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
|
# Useful constants and functions
|
||||||
#
|
#
|
||||||
|
|
||||||
WSP = set(' \t')
|
_WSP = ' \t'
|
||||||
|
WSP = set(_WSP)
|
||||||
CFWS_LEADER = WSP | set('(')
|
CFWS_LEADER = WSP | set('(')
|
||||||
SPECIALS = set(r'()<>@,:;.\"[]')
|
SPECIALS = set(r'()<>@,:;.\"[]')
|
||||||
ATOM_ENDS = SPECIALS | WSP
|
ATOM_ENDS = SPECIALS | WSP
|
||||||
@@ -101,6 +102,12 @@ def make_quoted_pairs(value):
|
|||||||
return str(value).replace('\\', '\\\\').replace('"', '\\"')
|
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):
|
def quote_string(value):
|
||||||
escaped = make_quoted_pairs(value)
|
escaped = make_quoted_pairs(value)
|
||||||
return f'"{escaped}"'
|
return f'"{escaped}"'
|
||||||
@@ -632,11 +639,11 @@ class LocalPart(TokenList):
|
|||||||
for tok in self[0] + [DOT]:
|
for tok in self[0] + [DOT]:
|
||||||
if tok.token_type == 'cfws':
|
if tok.token_type == 'cfws':
|
||||||
continue
|
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'):
|
last[-1].token_type == 'cfws'):
|
||||||
res[-1] = TokenList(last[:-1])
|
res[-1] = TokenList(last[:-1])
|
||||||
is_tl = isinstance(tok, TokenList)
|
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'):
|
tok[0].token_type == 'cfws'):
|
||||||
res.append(TokenList(tok[1:]))
|
res.append(TokenList(tok[1:]))
|
||||||
else:
|
else:
|
||||||
@@ -874,6 +881,12 @@ class MessageID(MsgID):
|
|||||||
class InvalidMessageID(MessageID):
|
class InvalidMessageID(MessageID):
|
||||||
token_type = 'invalid-message-id'
|
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):
|
class Header(TokenList):
|
||||||
token_type = 'header'
|
token_type = 'header'
|
||||||
@@ -933,7 +946,7 @@ class WhiteSpaceTerminal(Terminal):
|
|||||||
return ' '
|
return ' '
|
||||||
|
|
||||||
def startswith_fws(self):
|
def startswith_fws(self):
|
||||||
return True
|
return self and self[0] in WSP
|
||||||
|
|
||||||
|
|
||||||
class ValueTerminal(Terminal):
|
class ValueTerminal(Terminal):
|
||||||
@@ -1232,8 +1245,7 @@ def get_bare_quoted_string(value):
|
|||||||
bare_quoted_string = BareQuotedString()
|
bare_quoted_string = BareQuotedString()
|
||||||
value = value[1:]
|
value = value[1:]
|
||||||
if value and value[0] == '"':
|
if value and value[0] == '"':
|
||||||
token, value = get_qcontent(value)
|
return bare_quoted_string, value[1:]
|
||||||
bare_quoted_string.append(token)
|
|
||||||
while value and value[0] != '"':
|
while value and value[0] != '"':
|
||||||
if value[0] in WSP:
|
if value[0] in WSP:
|
||||||
token, value = get_fws(value)
|
token, value = get_fws(value)
|
||||||
@@ -2046,12 +2058,10 @@ def get_address_list(value):
|
|||||||
address_list.defects.append(errors.InvalidHeaderDefect(
|
address_list.defects.append(errors.InvalidHeaderDefect(
|
||||||
"invalid address in address-list"))
|
"invalid address in address-list"))
|
||||||
if value and value[0] != ',':
|
if value and value[0] != ',':
|
||||||
# Crap after address; treat it as an invalid mailbox.
|
# Crap after address: add it to the address list
|
||||||
# The mailbox info will still be available.
|
# as an invalid mailbox
|
||||||
mailbox = address_list[-1][0]
|
|
||||||
mailbox.token_type = 'invalid-mailbox'
|
|
||||||
token, value = get_invalid_mailbox(value, ',')
|
token, value = get_invalid_mailbox(value, ',')
|
||||||
mailbox.extend(token)
|
address_list.append(Address([token]))
|
||||||
address_list.defects.append(errors.InvalidHeaderDefect(
|
address_list.defects.append(errors.InvalidHeaderDefect(
|
||||||
"invalid address in address-list"))
|
"invalid address in address-list"))
|
||||||
if value: # Must be a , at this point.
|
if value: # Must be a , at this point.
|
||||||
@@ -2171,6 +2181,32 @@ def parse_message_id(value):
|
|||||||
|
|
||||||
return message_id
|
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
|
# 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
|
# 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:
|
if lines and lines[-1] and lines[-1][-1] in WSP:
|
||||||
wsp = lines[-1][-1]
|
wsp = lines[-1][-1]
|
||||||
lines[-1] = 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
|
return wsp
|
||||||
|
|
||||||
|
|
||||||
def _refold_parse_tree(parse_tree, *, policy):
|
def _refold_parse_tree(parse_tree, *, policy):
|
||||||
"""Return string of contents of parse_tree folded according to RFC rules.
|
"""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
|
maxlen = policy.max_line_length or sys.maxsize
|
||||||
encoding = 'utf-8' if policy.utf8 else 'us-ascii'
|
encoding = 'utf-8' if policy.utf8 else 'us-ascii'
|
||||||
lines = [''] # Folded lines to be output
|
lines = [''] # Folded lines to be output
|
||||||
leading_whitespace = '' # When we have whitespace between two encoded
|
last_word_is_ew = False
|
||||||
# words, we may need to encode the whitespace
|
last_ew = None # if there is an encoded word in the last line of lines,
|
||||||
# at the beginning of the second word.
|
# points to the encoded word's first character
|
||||||
last_ew = None # Points to the last encoded character if there's an ew on
|
|
||||||
# the line
|
|
||||||
last_charset = None
|
last_charset = None
|
||||||
wrap_as_ew_blocked = 0
|
wrap_as_ew_blocked = 0
|
||||||
want_encoding = False # This is set to True if we need to encode this part
|
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':
|
if part.token_type == 'mime-parameters':
|
||||||
# Mime parameter folding (using RFC2231) is extra special.
|
# Mime parameter folding (using RFC2231) is extra special.
|
||||||
_fold_mime_parameters(part, lines, maxlen, encoding)
|
_fold_mime_parameters(part, lines, maxlen, encoding)
|
||||||
|
last_word_is_ew = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if want_encoding and not wrap_as_ew_blocked:
|
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?
|
# XXX what if encoded_part has no leading FWS?
|
||||||
lines.append(newline)
|
lines.append(newline)
|
||||||
lines[-1] += encoded_part
|
lines[-1] += encoded_part
|
||||||
|
last_word_is_ew = False
|
||||||
continue
|
continue
|
||||||
# Either this is not a major syntactic break, so we don't
|
# 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
|
# 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 == 'unknown-8bit' or
|
||||||
last_charset == 'utf-8' and charset != 'us-ascii')):
|
last_charset == 'utf-8' and charset != 'us-ascii')):
|
||||||
last_ew = None
|
last_ew = None
|
||||||
last_ew = _fold_as_ew(tstr, lines, maxlen, last_ew,
|
last_ew = _fold_as_ew(
|
||||||
part.ew_combine_allowed, charset, leading_whitespace)
|
tstr,
|
||||||
# This whitespace has been added to the lines in _fold_as_ew()
|
lines,
|
||||||
# so clear it now.
|
maxlen,
|
||||||
leading_whitespace = ''
|
last_ew,
|
||||||
|
part.ew_combine_allowed,
|
||||||
|
charset,
|
||||||
|
last_word_is_ew,
|
||||||
|
)
|
||||||
|
last_word_is_ew = True
|
||||||
last_charset = charset
|
last_charset = charset
|
||||||
want_encoding = False
|
want_encoding = False
|
||||||
continue
|
continue
|
||||||
@@ -2888,28 +2933,19 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||||||
|
|
||||||
if len(tstr) <= maxlen - len(lines[-1]):
|
if len(tstr) <= maxlen - len(lines[-1]):
|
||||||
lines[-1] += tstr
|
lines[-1] += tstr
|
||||||
|
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# This part is too long to fit. The RFC wants us to break at
|
# This part is too long to fit. The RFC wants us to break at
|
||||||
# "major syntactic breaks", so unless we don't consider this
|
# "major syntactic breaks", so unless we don't consider this
|
||||||
# to be one, check if it will fit on the next line by itself.
|
# to be one, check if it will fit on the next line by itself.
|
||||||
leading_whitespace = ''
|
|
||||||
if (part.syntactic_break and
|
if (part.syntactic_break and
|
||||||
len(tstr) + 1 <= maxlen):
|
len(tstr) + 1 <= maxlen):
|
||||||
newline = _steal_trailing_WSP_if_exists(lines)
|
newline = _steal_trailing_WSP_if_exists(lines)
|
||||||
if newline or part.startswith_fws():
|
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)
|
lines.append(newline + tstr)
|
||||||
|
last_word_is_ew = (last_word_is_ew
|
||||||
whitespace_accumulator = []
|
and not bool(lines[-1].strip(_WSP)))
|
||||||
for char in lines[-1]:
|
|
||||||
if char not in WSP:
|
|
||||||
break
|
|
||||||
whitespace_accumulator.append(char)
|
|
||||||
leading_whitespace = ''.join(whitespace_accumulator)
|
|
||||||
last_ew = None
|
last_ew = None
|
||||||
continue
|
continue
|
||||||
if not hasattr(part, 'encode'):
|
if not hasattr(part, 'encode'):
|
||||||
@@ -2924,6 +2960,13 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||||||
[ValueTerminal(make_quoted_pairs(p), 'ptext')
|
[ValueTerminal(make_quoted_pairs(p), 'ptext')
|
||||||
for p in newparts] +
|
for p in newparts] +
|
||||||
[ValueTerminal('"', 'ptext')])
|
[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:
|
if not part.as_ew_allowed:
|
||||||
wrap_as_ew_blocked += 1
|
wrap_as_ew_blocked += 1
|
||||||
newparts.append(end_ew_not_allowed)
|
newparts.append(end_ew_not_allowed)
|
||||||
@@ -2942,10 +2985,11 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||||||
else:
|
else:
|
||||||
# We can't fold it onto the next line either...
|
# We can't fold it onto the next line either...
|
||||||
lines[-1] += tstr
|
lines[-1] += tstr
|
||||||
|
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
|
||||||
|
|
||||||
return policy.linesep.join(lines) + policy.linesep
|
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.
|
"""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.
|
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(
|
to_encode = str(
|
||||||
get_unstructured(lines[-1][last_ew:] + to_encode))
|
get_unstructured(lines[-1][last_ew:] + to_encode))
|
||||||
lines[-1] = lines[-1][:last_ew]
|
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:
|
elif to_encode[0] in WSP:
|
||||||
# We're joining this to non-encoded text, so don't encode
|
# We're joining this to non-encoded text, so don't encode
|
||||||
# the leading blank.
|
# the leading blank.
|
||||||
@@ -2988,20 +3042,13 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset,
|
|||||||
|
|
||||||
while to_encode:
|
while to_encode:
|
||||||
remaining_space = maxlen - len(lines[-1])
|
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:
|
if text_space <= 0:
|
||||||
lines.append(' ')
|
newline = _steal_trailing_WSP_if_exists(lines)
|
||||||
|
lines.append(newline or ' ')
|
||||||
|
new_last_ew = len(lines[-1])
|
||||||
continue
|
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]
|
to_encode_word = to_encode[:text_space]
|
||||||
encoded_word = _ew.encode(to_encode_word, charset=encode_as)
|
encoded_word = _ew.encode(to_encode_word, charset=encode_as)
|
||||||
excess = len(encoded_word) - remaining_space
|
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
|
excess = len(encoded_word) - remaining_space
|
||||||
lines[-1] += encoded_word
|
lines[-1] += encoded_word
|
||||||
to_encode = to_encode[len(to_encode_word):]
|
to_encode = to_encode[len(to_encode_word):]
|
||||||
leading_whitespace = ''
|
|
||||||
|
|
||||||
if to_encode:
|
if to_encode:
|
||||||
lines.append(' ')
|
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
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
"""Email address parsing code.
|
"""Email address parsing code.
|
||||||
@@ -225,7 +225,7 @@ class AddrlistClass:
|
|||||||
def __init__(self, field):
|
def __init__(self, field):
|
||||||
"""Initialize a new instance.
|
"""Initialize a new instance.
|
||||||
|
|
||||||
`field' is an unparsed address header field, containing
|
'field' is an unparsed address header field, containing
|
||||||
one or more addresses.
|
one or more addresses.
|
||||||
"""
|
"""
|
||||||
self.specials = '()<>@,:;.\"[]'
|
self.specials = '()<>@,:;.\"[]'
|
||||||
@@ -426,14 +426,14 @@ class AddrlistClass:
|
|||||||
def getdelimited(self, beginchar, endchars, allowcomments=True):
|
def getdelimited(self, beginchar, endchars, allowcomments=True):
|
||||||
"""Parse a header fragment delimited by special characters.
|
"""Parse a header fragment delimited by special characters.
|
||||||
|
|
||||||
`beginchar' is the start character for the fragment.
|
'beginchar' is the start character for the fragment.
|
||||||
If self is not looking at an instance of `beginchar' then
|
If self is not looking at an instance of 'beginchar' then
|
||||||
getdelimited returns the empty string.
|
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.
|
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.
|
within the parsed fragment.
|
||||||
"""
|
"""
|
||||||
if self.field[self.pos] != beginchar:
|
if self.field[self.pos] != beginchar:
|
||||||
@@ -477,7 +477,7 @@ class AddrlistClass:
|
|||||||
|
|
||||||
Optional atomends specifies a different set of end token delimiters
|
Optional atomends specifies a different set of end token delimiters
|
||||||
(the default is to use self.atomends). This is used e.g. in
|
(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)."""
|
is legal in phrases)."""
|
||||||
atomlist = ['']
|
atomlist = ['']
|
||||||
if atomends is None:
|
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 abc
|
||||||
|
import re
|
||||||
from email import header
|
from email import header
|
||||||
from email import charset as _charset
|
from email import charset as _charset
|
||||||
from email.utils import _has_surrogates
|
from email.utils import _has_surrogates
|
||||||
@@ -14,6 +15,14 @@ __all__ = [
|
|||||||
'compat32',
|
'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:
|
class _PolicyBase:
|
||||||
|
|
||||||
@@ -150,7 +159,7 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta):
|
|||||||
wrapping is done. Default is 78.
|
wrapping is done. Default is 78.
|
||||||
|
|
||||||
mangle_from_ -- a flag that, when True escapes From_ lines in the
|
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
|
them. This is used when the message is being
|
||||||
serialized by a generator. Default: False.
|
serialized by a generator. Default: False.
|
||||||
|
|
||||||
@@ -314,6 +323,7 @@ class Compat32(Policy):
|
|||||||
"""+
|
"""+
|
||||||
The name and value are returned unmodified.
|
The name and value are returned unmodified.
|
||||||
"""
|
"""
|
||||||
|
validate_header_name(name)
|
||||||
return (name, value)
|
return (name, value)
|
||||||
|
|
||||||
def header_fetch_parse(self, 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
|
# Author: Ben Gertzfield
|
||||||
# Contact: email-sig@python.org
|
# 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.
|
with Base64 encoding.
|
||||||
|
|
||||||
RFC 2045 defines a method for including character set information in an
|
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.
|
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
|
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
|
# Author: Ben Gertzfield, Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ class Charset:
|
|||||||
module expose the following information about a character set:
|
module expose the following information about a character set:
|
||||||
|
|
||||||
input_charset: The initial character set specified. Common aliases
|
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.
|
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
|
header_encoding: If the character set must be encoded before it can be
|
||||||
@@ -245,7 +245,7 @@ class Charset:
|
|||||||
def get_body_encoding(self):
|
def get_body_encoding(self):
|
||||||
"""Return the content-transfer-encoding used for body encoding.
|
"""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 encoding used, or it is a function in which case you should call
|
||||||
the function with a single argument, the Message object being
|
the function with a single argument, the Message object being
|
||||||
encoded. The function should then set the Content-Transfer-Encoding
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Authors: Baxter, Wouters and Warsaw
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ from io import StringIO
|
|||||||
|
|
||||||
NLCRE = re.compile(r'\r\n|\r|\n')
|
NLCRE = re.compile(r'\r\n|\r|\n')
|
||||||
NLCRE_bol = 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)')
|
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
|
# RFC 5322 section 3.6.8 Optional fields. ftext is %d33-57 / %d59-126, Any character
|
||||||
# except controls, SP, and ":".
|
# except controls, SP, and ":".
|
||||||
@@ -504,10 +504,9 @@ class FeedParser:
|
|||||||
self._input.unreadline(line)
|
self._input.unreadline(line)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# Weirdly placed unix-from line. Note this as a defect
|
# Weirdly placed unix-from line.
|
||||||
# and ignore it.
|
|
||||||
defect = errors.MisplacedEnvelopeHeaderDefect(line)
|
defect = errors.MisplacedEnvelopeHeaderDefect(line)
|
||||||
self._cur.defects.append(defect)
|
self.policy.handle_defect(self._cur, defect)
|
||||||
continue
|
continue
|
||||||
# Split the line on the colon separating field name from value.
|
# 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
|
# 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.
|
# message. Track the error but keep going.
|
||||||
if i == 0:
|
if i == 0:
|
||||||
defect = errors.InvalidHeaderDefect("Missing header name.")
|
defect = errors.InvalidHeaderDefect("Missing header name.")
|
||||||
self._cur.defects.append(defect)
|
self.policy.handle_defect(self._cur, defect)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
assert i>0, "_parse_headers fed line with no : and no leading WS"
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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')
|
NLCRE = re.compile(r'\r\n|\r|\n')
|
||||||
fcre = re.compile(r'^From ', re.MULTILINE)
|
fcre = re.compile(r'^From ', re.MULTILINE)
|
||||||
NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
|
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:
|
class Generator:
|
||||||
@@ -43,7 +44,7 @@ class Generator:
|
|||||||
|
|
||||||
Optional mangle_from_ is a flag that, when True (the default if policy
|
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
|
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
|
Optional maxheaderlen specifies the longest length for a non-continued
|
||||||
header. When a header line is longer (in characters, with tabs
|
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
|
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
|
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.
|
is False to inhibit the printing of any From_ delimiter.
|
||||||
|
|
||||||
Note that for subobjects, no From_ line is printed.
|
Note that for subobjects, no From_ line is printed.
|
||||||
@@ -227,7 +228,7 @@ class Generator:
|
|||||||
folded = self.policy.fold(h, v)
|
folded = self.policy.fold(h, v)
|
||||||
if self.policy.verify_generated_headers:
|
if self.policy.verify_generated_headers:
|
||||||
linesep = self.policy.linesep
|
linesep = self.policy.linesep
|
||||||
if not folded.endswith(self.policy.linesep):
|
if not folded.endswith(linesep):
|
||||||
raise HeaderWriteError(
|
raise HeaderWriteError(
|
||||||
f'folded header does not end with {linesep!r}: {folded!r}')
|
f'folded header does not end with {linesep!r}: {folded!r}')
|
||||||
if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)):
|
if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)):
|
||||||
@@ -391,7 +392,7 @@ class Generator:
|
|||||||
b = boundary
|
b = boundary
|
||||||
counter = 0
|
counter = 0
|
||||||
while True:
|
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):
|
if not cre.search(text):
|
||||||
break
|
break
|
||||||
b = boundary + '.' + str(counter)
|
b = boundary + '.' + str(counter)
|
||||||
@@ -429,7 +430,16 @@ class BytesGenerator(Generator):
|
|||||||
# This is almost the same as the string version, except for handling
|
# This is almost the same as the string version, except for handling
|
||||||
# strings with 8bit bytes.
|
# strings with 8bit bytes.
|
||||||
for h, v in msg.raw_items():
|
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
|
# A blank line always separates headers from body
|
||||||
self.write(self._NL)
|
self.write(self._NL)
|
||||||
|
|
||||||
@@ -467,7 +477,7 @@ class DecodedGenerator(Generator):
|
|||||||
argument is allowed.
|
argument is allowed.
|
||||||
|
|
||||||
Walks through all subparts of a message. If the subpart is of main
|
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
|
Otherwise, fmt is a format string that is used instead of the message
|
||||||
payload. fmt is expanded with the following keywords (in
|
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
|
# Author: Ben Gertzfield, Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ class Header:
|
|||||||
|
|
||||||
The maximum line length can be specified explicitly via maxlinelen. For
|
The maximum line length can be specified explicitly via maxlinelen. For
|
||||||
splitting the first line to a shorter value (to account for the field
|
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
|
the field in header_name. The default maxlinelen is 78 as recommended
|
||||||
by RFC 2822.
|
by RFC 2822.
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ class Header:
|
|||||||
output codec of the charset. If the string cannot be encoded to the
|
output codec of the charset. If the string cannot be encoded to the
|
||||||
output codec, a UnicodeError will be raised.
|
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.
|
call if s is a byte string.
|
||||||
"""
|
"""
|
||||||
if charset is None:
|
if charset is None:
|
||||||
@@ -335,7 +335,7 @@ class Header:
|
|||||||
|
|
||||||
Optional splitchars is a string containing characters which should be
|
Optional splitchars is a string containing characters which should be
|
||||||
given extra weight by the splitting algorithm during normal header
|
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
|
syntactic breaks': split points preceded by a splitchar are preferred
|
||||||
during line splitting, with the characters preferred in the order in
|
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
|
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)
|
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 #
|
# The header factory #
|
||||||
|
|
||||||
_default_header_map = {
|
_default_header_map = {
|
||||||
@@ -557,6 +569,8 @@ _default_header_map = {
|
|||||||
'content-disposition': ContentDispositionHeader,
|
'content-disposition': ContentDispositionHeader,
|
||||||
'content-transfer-encoding': ContentTransferEncodingHeader,
|
'content-transfer-encoding': ContentTransferEncodingHeader,
|
||||||
'message-id': MessageIDHeader,
|
'message-id': MessageIDHeader,
|
||||||
|
'in-reply-to': ReferencesHeader,
|
||||||
|
'references': ReferencesHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
class HeaderRegistry:
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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):
|
def typed_subpart_iterator(msg, maintype='text', subtype=None):
|
||||||
"""Iterate over the subparts with a given MIME type.
|
"""Iterate over the subparts with a given MIME type.
|
||||||
|
|
||||||
Use `maintype' as the main MIME type to match against; this defaults to
|
Use 'maintype' as the main MIME type to match against; this defaults to
|
||||||
"text". Optional `subtype' is the MIME subtype to match against; if
|
"text". Optional 'subtype' is the MIME subtype to match against; if
|
||||||
omitted, only the main type is matched.
|
omitted, only the main type is matched.
|
||||||
"""
|
"""
|
||||||
for subpart in msg.walk():
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ Charset = _charset.Charset
|
|||||||
|
|
||||||
SEMISPACE = '; '
|
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.
|
# existence of which force quoting of the parameter value.
|
||||||
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
|
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class Message:
|
|||||||
multipart or a message/rfc822), then the payload is a list of Message
|
multipart or a message/rfc822), then the payload is a list of Message
|
||||||
objects, otherwise it is a string.
|
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
|
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,
|
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
|
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.
|
"""Return the message's content type.
|
||||||
|
|
||||||
The returned string is coerced to lower case of the form
|
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
|
message, the default type as given by get_default_type() will be
|
||||||
returned. Since according to RFC 2045, messages always have a default
|
returned. Since according to RFC 2045, messages always have a default
|
||||||
type this will always return a value.
|
type this will always return a value.
|
||||||
@@ -632,7 +632,7 @@ class Message:
|
|||||||
def get_content_maintype(self):
|
def get_content_maintype(self):
|
||||||
"""Return the message's main content type.
|
"""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().
|
get_content_type().
|
||||||
"""
|
"""
|
||||||
ctype = self.get_content_type()
|
ctype = self.get_content_type()
|
||||||
@@ -641,14 +641,14 @@ class Message:
|
|||||||
def get_content_subtype(self):
|
def get_content_subtype(self):
|
||||||
"""Returns the message's sub-content type.
|
"""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().
|
get_content_type().
|
||||||
"""
|
"""
|
||||||
ctype = self.get_content_type()
|
ctype = self.get_content_type()
|
||||||
return ctype.split('/')[1]
|
return ctype.split('/')[1]
|
||||||
|
|
||||||
def get_default_type(self):
|
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
|
Most messages have a default content type of text/plain, except for
|
||||||
messages that are subparts of multipart/digest containers. Such
|
messages that are subparts of multipart/digest containers. Such
|
||||||
@@ -657,7 +657,7 @@ class Message:
|
|||||||
return self._default_type
|
return self._default_type
|
||||||
|
|
||||||
def set_default_type(self, ctype):
|
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
|
ctype should be either "text/plain" or "message/rfc822", although this
|
||||||
is not enforced. The default content type is not stored in the
|
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.
|
"""Return the message's Content-Type parameters, as a list.
|
||||||
|
|
||||||
The elements of the returned list are 2-tuples of key/value pairs, as
|
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,
|
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
|
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
|
the parameter the value is the empty string. The value is as
|
||||||
described in the get_param() method.
|
described in the get_param() method.
|
||||||
|
|
||||||
@@ -851,9 +851,9 @@ class Message:
|
|||||||
"""Return the filename associated with the payload if present.
|
"""Return the filename associated with the payload if present.
|
||||||
|
|
||||||
The filename is extracted from the Content-Disposition header's
|
The filename is extracted from the Content-Disposition header's
|
||||||
`filename' parameter, and it is unquoted. If that header is missing
|
'filename' parameter, and it is unquoted. If that header is missing
|
||||||
the `filename' parameter, this method falls back to looking for the
|
the 'filename' parameter, this method falls back to looking for the
|
||||||
`name' parameter.
|
'name' parameter.
|
||||||
"""
|
"""
|
||||||
missing = object()
|
missing = object()
|
||||||
filename = self.get_param('filename', missing, 'content-disposition')
|
filename = self.get_param('filename', missing, 'content-disposition')
|
||||||
@@ -866,7 +866,7 @@ class Message:
|
|||||||
def get_boundary(self, failobj=None):
|
def get_boundary(self, failobj=None):
|
||||||
"""Return the boundary associated with the payload if present.
|
"""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.
|
parameter, and it is unquoted.
|
||||||
"""
|
"""
|
||||||
missing = object()
|
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
|
# Author: Keith Dart
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Anthony Baxter
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class MIMEMultipart(MIMEBase):
|
|||||||
Content-Type and MIME-Version headers.
|
Content-Type and MIME-Version headers.
|
||||||
|
|
||||||
_subtype is the subtype of the multipart content type, defaulting to
|
_subtype is the subtype of the multipart content type, defaulting to
|
||||||
`mixed'.
|
'mixed'.
|
||||||
|
|
||||||
boundary is the multipart boundary string. By default it is
|
boundary is the multipart boundary string. By default it is
|
||||||
calculated as needed.
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# 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
|
# Author: Barry Warsaw, Thomas Wouters, Anthony Baxter
|
||||||
# Contact: email-sig@python.org
|
# 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 re
|
||||||
import sys
|
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.utils import _has_surrogates
|
||||||
from email.headerregistry import HeaderRegistry as HeaderRegistry
|
from email.headerregistry import HeaderRegistry as HeaderRegistry
|
||||||
from email.contentmanager import raw_data_manager
|
from email.contentmanager import raw_data_manager
|
||||||
@@ -138,6 +144,7 @@ class EmailPolicy(Policy):
|
|||||||
CR or LF characters.
|
CR or LF characters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
validate_header_name(name)
|
||||||
if hasattr(value, 'name') and value.name.lower() == name.lower():
|
if hasattr(value, 'name') and value.name.lower() == name.lower():
|
||||||
return (name, value)
|
return (name, value)
|
||||||
if isinstance(value, str) and len(value.splitlines())>1:
|
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
|
# Author: Ben Gertzfield
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
"""Quoted-printable content transfer encoding per RFCs 2045-2047.
|
"""Quoted-printable content transfer encoding per RFCs 2045-2047.
|
||||||
|
|
||||||
This module handles the content transfer encoding method defined in RFC 2045
|
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
|
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
|
character set, but that includes some 8-bit characters that are normally not
|
||||||
allowed in email bodies or headers.
|
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.
|
with quoted-printable encoding.
|
||||||
|
|
||||||
RFC 2045 defines a method for including character set information in an
|
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.
|
in To:/From:/Cc: etc. fields, as well as Subject: lines.
|
||||||
|
|
||||||
This module does not do the line wrapping or end-of-line character
|
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'):
|
def header_encode(header_bytes, charset='iso-8859-1'):
|
||||||
"""Encode a single header line with quoted-printable (like) encoding.
|
"""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
|
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
|
bit characters (and some 8 bit) to remain more or less readable in non-RFC
|
||||||
2045 aware mail clients.
|
2045 aware mail clients.
|
||||||
@@ -272,7 +272,7 @@ def decode(encoded, eol=NL):
|
|||||||
decoded += eol
|
decoded += eol
|
||||||
# Special case if original string did not end with eol
|
# Special case if original string did not end with eol
|
||||||
if encoded[-1] not in '\r\n' and decoded.endswith(eol):
|
if encoded[-1] not in '\r\n' and decoded.endswith(eol):
|
||||||
decoded = decoded[:-1]
|
decoded = decoded[:-len(eol)]
|
||||||
return decoded
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ def _unquote_match(match):
|
|||||||
|
|
||||||
# Header decoding is done a bit differently
|
# Header decoding is done a bit differently
|
||||||
def header_decode(s):
|
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
|
This function does not parse a full MIME header value encoded with
|
||||||
quoted-printable (like =?iso-8859-1?q?Hello_World?=) -- please use
|
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
|
# Author: Barry Warsaw
|
||||||
# Contact: email-sig@python.org
|
# Contact: email-sig@python.org
|
||||||
|
|
||||||
@@ -472,23 +472,15 @@ def collapse_rfc2231_value(value, errors='replace',
|
|||||||
# better than not having it.
|
# better than not having it.
|
||||||
#
|
#
|
||||||
|
|
||||||
def localtime(dt=None, isdst=None):
|
def localtime(dt=None):
|
||||||
"""Return local time as an aware datetime object.
|
"""Return local time as an aware datetime object.
|
||||||
|
|
||||||
If called without arguments, return current time. Otherwise *dt*
|
If called without arguments, return current time. Otherwise *dt*
|
||||||
argument should be a datetime instance, and it is converted to the
|
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
|
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.
|
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:
|
if dt is None:
|
||||||
dt = datetime.datetime.now()
|
dt = datetime.datetime.now()
|
||||||
return dt.astimezone()
|
return dt.astimezone()
|
||||||
|
|||||||
5
Lib/encodings/__init__.py
vendored
5
Lib/encodings/__init__.py
vendored
@@ -33,6 +33,7 @@ import sys
|
|||||||
from . import aliases
|
from . import aliases
|
||||||
|
|
||||||
_cache = {}
|
_cache = {}
|
||||||
|
_MAXCACHE = 500
|
||||||
_unknown = '--unknown--'
|
_unknown = '--unknown--'
|
||||||
_import_tail = ['*']
|
_import_tail = ['*']
|
||||||
_aliases = aliases.aliases
|
_aliases = aliases.aliases
|
||||||
@@ -115,6 +116,8 @@ def search_function(encoding):
|
|||||||
|
|
||||||
if mod is None:
|
if mod is None:
|
||||||
# Cache misses
|
# Cache misses
|
||||||
|
if len(_cache) >= _MAXCACHE:
|
||||||
|
_cache.clear()
|
||||||
_cache[encoding] = None
|
_cache[encoding] = None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -136,6 +139,8 @@ def search_function(encoding):
|
|||||||
entry = codecs.CodecInfo(*entry)
|
entry = codecs.CodecInfo(*entry)
|
||||||
|
|
||||||
# Cache the codec registry entry
|
# Cache the codec registry entry
|
||||||
|
if len(_cache) >= _MAXCACHE:
|
||||||
|
_cache.clear()
|
||||||
_cache[encoding] = entry
|
_cache[encoding] = entry
|
||||||
|
|
||||||
# Register its aliases (without overwriting previously registered
|
# 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"]
|
__all__ = ["version", "bootstrap"]
|
||||||
_PIP_VERSION = "25.3"
|
_PIP_VERSION = "26.1.1"
|
||||||
|
|
||||||
# Directory of system wheel packages. Some Linux distribution packaging
|
# Directory of system wheel packages. Some Linux distribution packaging
|
||||||
# policies recommend against bundling dependencies. For example, Fedora
|
# policies recommend against bundling dependencies. For example, Fedora
|
||||||
# installs wheel packages in the /usr/share/python-wheels/ directory and don't
|
# installs wheel packages in the /usr/share/python-wheels/ directory and don't
|
||||||
# install the ensurepip._bundled package.
|
# 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()
|
_WHEEL_PKG_DIR = Path(_pkg_dir).resolve()
|
||||||
else:
|
else:
|
||||||
_WHEEL_PKG_DIR = None
|
_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,
|
def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||||
include_hidden=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
|
The pattern may contain simple shell-style wildcards a la
|
||||||
fnmatch. Unlike fnmatch, filenames starting with a
|
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
|
The order of the returned list is undefined. Sort it if you need a
|
||||||
particular order.
|
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
|
If `include_hidden` is true, the patterns '*', '?', '**' will match hidden
|
||||||
directories.
|
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,
|
def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
|
||||||
include_hidden=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
|
The pattern may contain simple shell-style wildcards a la
|
||||||
fnmatch. However, unlike fnmatch, filenames starting with a
|
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
|
The order of the returned paths is undefined. Sort them if you need a
|
||||||
particular order.
|
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.
|
zero or more directories and subdirectories.
|
||||||
"""
|
"""
|
||||||
sys.audit("glob.glob", pathname, recursive)
|
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
|
return ip
|
||||||
|
|
||||||
def _tunnel(self):
|
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" % (
|
connect = b"CONNECT %s:%d %s\r\n" % (
|
||||||
self._wrap_ipv6(self._tunnel_host.encode("idna")),
|
self._wrap_ipv6(self._tunnel_host.encode("idna")),
|
||||||
self._tunnel_port,
|
self._tunnel_port,
|
||||||
self._http_vsn_str.encode("ascii"))
|
self._http_vsn_str.encode("ascii"))
|
||||||
headers = [connect]
|
headers = [connect]
|
||||||
for header, value in self._tunnel_headers.items():
|
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")
|
headers.append(b"\r\n")
|
||||||
# Making a single send() call instead of one per line encourages
|
# Making a single send() call instead of one per line encourages
|
||||||
# the host OS to use a more optimal packet size instead of
|
# 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()
|
key = key.lower()
|
||||||
if key not in self._reserved:
|
if key not in self._reserved:
|
||||||
raise CookieError("Invalid attribute %r" % (key,))
|
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
|
data[key] = val
|
||||||
dict.update(self, data)
|
dict.update(self, data)
|
||||||
|
|
||||||
|
def __ior__(self, values):
|
||||||
|
self.update(values)
|
||||||
|
return self
|
||||||
|
|
||||||
def isReservedKey(self, K):
|
def isReservedKey(self, K):
|
||||||
return K.lower() in self._reserved
|
return K.lower() in self._reserved
|
||||||
|
|
||||||
@@ -365,9 +372,15 @@ class Morsel(dict):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self._key = state['key']
|
key = state['key']
|
||||||
self._value = state['value']
|
value = state['value']
|
||||||
self._coded_value = state['coded_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:"):
|
def output(self, attrs=None, header="Set-Cookie:"):
|
||||||
return "%s %s" % (header, self.OutputString(attrs))
|
return "%s %s" % (header, self.OutputString(attrs))
|
||||||
@@ -378,14 +391,21 @@ class Morsel(dict):
|
|||||||
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
|
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
|
||||||
|
|
||||||
def js_output(self, attrs=None):
|
def js_output(self, attrs=None):
|
||||||
|
import base64
|
||||||
# Print javascript
|
# 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 """
|
return """
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
<!-- begin hiding
|
<!-- begin hiding
|
||||||
document.cookie = \"%s\";
|
document.cookie = atob(\"%s\");
|
||||||
// end hiding -->
|
// end hiding -->
|
||||||
</script>
|
</script>
|
||||||
""" % (self.OutputString(attrs).replace('"', r'\"'))
|
""" % (output_encoded,)
|
||||||
|
|
||||||
def OutputString(self, attrs=None):
|
def OutputString(self, attrs=None):
|
||||||
# Build up our result
|
# 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.
|
"""Get useful information from live Python objects.
|
||||||
|
|
||||||
This module encapsulates the interface provided by the internal special
|
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.
|
It also provides some help for examining source code and class layout.
|
||||||
|
|
||||||
Here are some of the useful functions provided by this module:
|
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
|
The annotation for the parameter if specified. If the
|
||||||
parameter has no annotation, this attribute is set to
|
parameter has no annotation, this attribute is set to
|
||||||
`Parameter.empty`.
|
`Parameter.empty`.
|
||||||
* kind : str
|
* kind
|
||||||
Describes how argument values are bound to the parameter.
|
Describes how argument values are bound to the parameter.
|
||||||
Possible values: `Parameter.POSITIONAL_ONLY`,
|
Possible values: `Parameter.POSITIONAL_ONLY`,
|
||||||
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
|
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
|
||||||
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
|
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
|
||||||
|
Every value has a `description` attribute describing meaning.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_name', '_kind', '_default', '_annotation')
|
__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.
|
level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.
|
||||||
There is no arbitrary limit to the depth of nesting.
|
There is no arbitrary limit to the depth of nesting.
|
||||||
"""
|
"""
|
||||||
_tls = threading.local()
|
|
||||||
|
|
||||||
def __init__(self, name, level=NOTSET):
|
def __init__(self, name, level=NOTSET):
|
||||||
"""
|
"""
|
||||||
Initialize the logger with a name and an optional level.
|
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
|
This method is used for unpickled records received from a socket, as
|
||||||
well as those created locally. Logger-level filtering is applied.
|
well as those created locally. Logger-level filtering is applied.
|
||||||
"""
|
"""
|
||||||
if self._is_disabled():
|
if self.disabled:
|
||||||
return
|
return
|
||||||
|
maybe_record = self.filter(record)
|
||||||
self._tls.in_progress = True
|
if not maybe_record:
|
||||||
try:
|
return
|
||||||
maybe_record = self.filter(record)
|
if isinstance(maybe_record, LogRecord):
|
||||||
if not maybe_record:
|
record = maybe_record
|
||||||
return
|
self.callHandlers(record)
|
||||||
if isinstance(maybe_record, LogRecord):
|
|
||||||
record = maybe_record
|
|
||||||
self.callHandlers(record)
|
|
||||||
finally:
|
|
||||||
self._tls.in_progress = False
|
|
||||||
|
|
||||||
def addHandler(self, hdlr):
|
def addHandler(self, hdlr):
|
||||||
"""
|
"""
|
||||||
@@ -1773,7 +1766,7 @@ class Logger(Filterer):
|
|||||||
"""
|
"""
|
||||||
Is this logger enabled for level 'level'?
|
Is this logger enabled for level 'level'?
|
||||||
"""
|
"""
|
||||||
if self._is_disabled():
|
if self.disabled:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1823,11 +1816,6 @@ class Logger(Filterer):
|
|||||||
if isinstance(item, Logger) and item.parent is self and
|
if isinstance(item, Logger) and item.parent is self and
|
||||||
_hierlevel(item) == 1 + _hierlevel(item.parent))
|
_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):
|
def __repr__(self):
|
||||||
level = getLevelName(self.getEffectiveLevel())
|
level = getLevelName(self.getEffectiveLevel())
|
||||||
return '<%s %s (%s)>' % (self.__class__.__name__, self.name, level)
|
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):
|
def __init__(self, logger, extra=None, merge_extra=False):
|
||||||
"""
|
"""
|
||||||
Initialize the adapter with a logger and a dict-like object which
|
Initialize the adapter with a logger and an optional dict-like object
|
||||||
provides contextual information. This constructor signature allows
|
which provides contextual information. This constructor signature
|
||||||
easy stacking of LoggerAdapters, if so desired.
|
allows easy stacking of LoggerAdapters, if so desired.
|
||||||
|
|
||||||
You can effectively pass keyword arguments as shown in the
|
You can effectively pass keyword arguments as shown in the
|
||||||
following example:
|
following example:
|
||||||
@@ -1897,8 +1885,9 @@ class LoggerAdapter(object):
|
|||||||
Normally, you'll only need to override this one method in a
|
Normally, you'll only need to override this one method in a
|
||||||
LoggerAdapter subclass for your specific needs.
|
LoggerAdapter subclass for your specific needs.
|
||||||
"""
|
"""
|
||||||
if self.merge_extra and "extra" in kwargs:
|
if self.merge_extra and kwargs.get("extra") is not None:
|
||||||
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
|
if self.extra is not None:
|
||||||
|
kwargs["extra"] = {**self.extra, **kwargs["extra"]}
|
||||||
else:
|
else:
|
||||||
kwargs["extra"] = self.extra
|
kwargs["extra"] = self.extra
|
||||||
return msg, kwargs
|
return msg, kwargs
|
||||||
|
|||||||
14
Lib/logging/config.py
vendored
14
Lib/logging/config.py
vendored
@@ -865,6 +865,8 @@ class DictConfigurator(BaseConfigurator):
|
|||||||
else:
|
else:
|
||||||
factory = klass
|
factory = klass
|
||||||
kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
|
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:
|
try:
|
||||||
result = factory(**kwargs)
|
result = factory(**kwargs)
|
||||||
except TypeError as te:
|
except TypeError as te:
|
||||||
@@ -876,6 +878,15 @@ class DictConfigurator(BaseConfigurator):
|
|||||||
#(e.g. by Django)
|
#(e.g. by Django)
|
||||||
kwargs['strm'] = kwargs.pop('stream')
|
kwargs['strm'] = kwargs.pop('stream')
|
||||||
result = factory(**kwargs)
|
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:
|
if formatter:
|
||||||
result.setFormatter(formatter)
|
result.setFormatter(formatter)
|
||||||
if level is not None:
|
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.
|
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,
|
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
|
||||||
handler=None, ready=None, verify=None):
|
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...
|
if self.stream is None: # delay was set...
|
||||||
self.stream = self._open()
|
self.stream = self._open()
|
||||||
if self.maxBytes > 0: # are we rolling over?
|
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:
|
if not pos:
|
||||||
# gh-116263: Never rollover an empty file
|
# gh-116263: Never rollover an empty file
|
||||||
return False
|
return False
|
||||||
@@ -855,7 +859,7 @@ class SysLogHandler(logging.Handler):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
|
def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
|
||||||
facility=LOG_USER, socktype=None):
|
facility=LOG_USER, socktype=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
Initialize a handler.
|
Initialize a handler.
|
||||||
|
|
||||||
@@ -872,6 +876,7 @@ class SysLogHandler(logging.Handler):
|
|||||||
self.address = address
|
self.address = address
|
||||||
self.facility = facility
|
self.facility = facility
|
||||||
self.socktype = socktype
|
self.socktype = socktype
|
||||||
|
self.timeout = timeout
|
||||||
self.socket = None
|
self.socket = None
|
||||||
self.createSocket()
|
self.createSocket()
|
||||||
|
|
||||||
@@ -933,6 +938,8 @@ class SysLogHandler(logging.Handler):
|
|||||||
err = sock = None
|
err = sock = None
|
||||||
try:
|
try:
|
||||||
sock = socket.socket(af, socktype, proto)
|
sock = socket.socket(af, socktype, proto)
|
||||||
|
if self.timeout:
|
||||||
|
sock.settimeout(self.timeout)
|
||||||
if socktype == socket.SOCK_STREAM:
|
if socktype == socket.SOCK_STREAM:
|
||||||
sock.connect(sa)
|
sock.connect(sa)
|
||||||
break
|
break
|
||||||
@@ -1529,6 +1536,19 @@ class QueueListener(object):
|
|||||||
self._thread = None
|
self._thread = None
|
||||||
self.respect_handler_level = respect_handler_level
|
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):
|
def dequeue(self, block):
|
||||||
"""
|
"""
|
||||||
Dequeue a record and return it, optionally blocking.
|
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
|
# Write data in-band
|
||||||
# XXX The C implementation avoids a copy here
|
# XXX The C implementation avoids a copy here
|
||||||
buf = m.tobytes()
|
buf = m.tobytes()
|
||||||
in_memo = id(buf) in self.memo
|
|
||||||
if m.readonly:
|
if m.readonly:
|
||||||
if in_memo:
|
self._save_bytes_no_memo(buf)
|
||||||
self._save_bytes_no_memo(buf)
|
|
||||||
else:
|
|
||||||
self.save_bytes(buf)
|
|
||||||
else:
|
else:
|
||||||
if in_memo:
|
self._save_bytearray_no_memo(buf)
|
||||||
self._save_bytearray_no_memo(buf)
|
self.memoize(obj)
|
||||||
else:
|
|
||||||
self.save_bytearray(buf)
|
|
||||||
else:
|
else:
|
||||||
# Write data out-of-band
|
# Write data out-of-band
|
||||||
self.write(NEXT_BUFFER)
|
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
|
""" This module tries to retrieve as much platform-identifying data as
|
||||||
possible. It makes this information available via function APIs.
|
possible. It makes this information available via function APIs.
|
||||||
|
|
||||||
@@ -33,6 +31,7 @@
|
|||||||
#
|
#
|
||||||
# <see CVS and SVN checkin messages for history>
|
# <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.8 - changed Windows support to read version from kernel32.dll
|
||||||
# 1.0.7 - added DEV_NULL
|
# 1.0.7 - added DEV_NULL
|
||||||
# 1.0.6 - added linux_distribution()
|
# 1.0.6 - added linux_distribution()
|
||||||
@@ -111,7 +110,7 @@ __copyright__ = """
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '1.0.8'
|
__version__ = '1.0.9'
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import os
|
import os
|
||||||
@@ -174,6 +173,11 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not executable:
|
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:
|
try:
|
||||||
ver = os.confstr('CS_GNU_LIBC_VERSION')
|
ver = os.confstr('CS_GNU_LIBC_VERSION')
|
||||||
# parse 'glibc 2.28' as ('glibc', '2.28')
|
# 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.
|
# sys.executable is not set.
|
||||||
return lib, version
|
return lib, version
|
||||||
|
|
||||||
libc_search = re.compile(b'(__libc_init)'
|
libc_search = re.compile(br"""
|
||||||
b'|'
|
(__libc_init)
|
||||||
b'(GLIBC_([0-9.]+))'
|
| (GLIBC_([0-9.]+))
|
||||||
b'|'
|
| (libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)
|
||||||
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
|
| (musl-([0-9.]+))
|
||||||
|
| ((?:libc\.|ld-)musl(?:-\w+)?.so(?:\.(\d[0-9.]*))?)
|
||||||
|
""",
|
||||||
|
re.ASCII | re.VERBOSE)
|
||||||
|
|
||||||
V = _comparable_version
|
V = _comparable_version
|
||||||
# We use os.path.realpath()
|
# We use os.path.realpath()
|
||||||
# here to work around problems with Cygwin not being
|
# here to work around problems with Cygwin not being
|
||||||
# able to open symlinks for reading
|
# able to open symlinks for reading
|
||||||
executable = os.path.realpath(executable)
|
executable = os.path.realpath(executable)
|
||||||
|
ver = None
|
||||||
with open(executable, 'rb') as f:
|
with open(executable, 'rb') as f:
|
||||||
binary = f.read(chunksize)
|
binary = f.read(chunksize)
|
||||||
pos = 0
|
pos = 0
|
||||||
while pos < len(binary):
|
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)
|
m = libc_search.search(binary, pos)
|
||||||
else:
|
else:
|
||||||
m = None
|
m = None
|
||||||
@@ -217,26 +225,35 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
|
|||||||
continue
|
continue
|
||||||
if not m:
|
if not m:
|
||||||
break
|
break
|
||||||
libcinit, glibc, glibcversion, so, threads, soversion = [
|
decoded_groups = [s.decode('latin1') if s is not None else s
|
||||||
s.decode('latin1') if s is not None else s
|
for s in m.groups()]
|
||||||
for s in m.groups()]
|
(libcinit, glibc, glibcversion, so, threads, soversion,
|
||||||
|
musl, muslversion, musl_so, musl_sover) = decoded_groups
|
||||||
if libcinit and not lib:
|
if libcinit and not lib:
|
||||||
lib = 'libc'
|
lib = 'libc'
|
||||||
elif glibc:
|
elif glibc:
|
||||||
if lib != 'glibc':
|
if lib != 'glibc':
|
||||||
lib = 'glibc'
|
lib = 'glibc'
|
||||||
version = glibcversion
|
ver = glibcversion
|
||||||
elif V(glibcversion) > V(version):
|
elif V(glibcversion) > V(ver):
|
||||||
version = glibcversion
|
ver = glibcversion
|
||||||
elif so:
|
elif so:
|
||||||
if lib != 'glibc':
|
if lib not in ('glibc', 'musl'):
|
||||||
lib = 'libc'
|
lib = 'libc'
|
||||||
if soversion and (not version or V(soversion) > V(version)):
|
if soversion and (not ver or V(soversion) > V(ver)):
|
||||||
version = soversion
|
ver = soversion
|
||||||
if threads and version[-len(threads):] != threads:
|
if threads and ver[-len(threads):] != threads:
|
||||||
version = version + 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()
|
pos = m.end()
|
||||||
return lib, version
|
return lib, version if ver is None else ver
|
||||||
|
|
||||||
def _norm_version(version, build=''):
|
def _norm_version(version, build=''):
|
||||||
|
|
||||||
@@ -549,7 +566,7 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
|
|||||||
warnings._deprecated('java_ver', remove=(3, 15))
|
warnings._deprecated('java_ver', remove=(3, 15))
|
||||||
# Import the needed APIs
|
# Import the needed APIs
|
||||||
try:
|
try:
|
||||||
import java.lang
|
import java.lang # noqa: F401
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return release, vendor, vminfo, osinfo
|
return release, vendor, vminfo, osinfo
|
||||||
|
|
||||||
@@ -1192,7 +1209,7 @@ def _sys_version(sys_version=None):
|
|||||||
# CPython
|
# CPython
|
||||||
cpython_sys_version_parser = re.compile(
|
cpython_sys_version_parser = re.compile(
|
||||||
r'([\w.+]+)\s*' # "version<space>"
|
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'\(#?([^,]+)' # "(#buildno"
|
||||||
r'(?:,\s*([\w ]*)' # ", builddate"
|
r'(?:,\s*([\w ]*)' # ", builddate"
|
||||||
r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
|
r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
|
||||||
@@ -1449,11 +1466,55 @@ def freedesktop_os_release():
|
|||||||
return _os_release_cache.copy()
|
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
|
### Command line interface
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def _parse_args(args: list[str] | None):
|
||||||
# Default is to print the aliased verbose platform string
|
import argparse
|
||||||
terse = ('terse' in sys.argv or '--terse' in sys.argv)
|
|
||||||
aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
|
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))
|
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:
|
Generate Plist example:
|
||||||
|
|
||||||
import datetime
|
import datetime as dt
|
||||||
import plistlib
|
import plistlib
|
||||||
|
|
||||||
pl = dict(
|
pl = dict(
|
||||||
@@ -37,7 +37,7 @@ Generate Plist example:
|
|||||||
),
|
),
|
||||||
someData = b"<binary gunk>",
|
someData = b"<binary gunk>",
|
||||||
someMoreData = b"<lots of binary gunk>" * 10,
|
someMoreData = b"<lots of binary gunk>" * 10,
|
||||||
aDate = datetime.datetime.now()
|
aDate = dt.datetime.now()
|
||||||
)
|
)
|
||||||
print(plistlib.dumps(pl).decode())
|
print(plistlib.dumps(pl).decode())
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ class _PlistWriter(_DumbXMLWriter):
|
|||||||
self._indent_level -= 1
|
self._indent_level -= 1
|
||||||
maxlinelength = max(
|
maxlinelength = max(
|
||||||
16,
|
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"):
|
for line in _encode_base64(data, maxlinelength).split(b"\n"):
|
||||||
if line:
|
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):
|
except (AttributeError, OSError):
|
||||||
pass
|
pass
|
||||||
master_fd, slave_name = _open_terminal()
|
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:
|
try:
|
||||||
master_fd, slave_fd = os.openpty()
|
from fcntl import ioctl, I_PUSH
|
||||||
except (AttributeError, OSError):
|
except ImportError:
|
||||||
|
return master_fd, slave_fd
|
||||||
|
try:
|
||||||
|
ioctl(slave_fd, I_PUSH, "ptem")
|
||||||
|
ioctl(slave_fd, I_PUSH, "ldterm")
|
||||||
|
except OSError:
|
||||||
pass
|
pass
|
||||||
else:
|
return master_fd, slave_fd
|
||||||
slave_name = os.ttyname(slave_fd)
|
|
||||||
os.close(slave_fd)
|
|
||||||
return master_fd, slave_name
|
|
||||||
|
|
||||||
return _open_terminal()
|
|
||||||
|
|
||||||
def _open_terminal():
|
def _open_terminal():
|
||||||
"""Open pty master and return (master_fd, tty_name)."""
|
"""Open pty master and return (master_fd, tty_name)."""
|
||||||
@@ -66,26 +57,6 @@ def _open_terminal():
|
|||||||
return (fd, '/dev/tty' + x + y)
|
return (fd, '/dev/tty' + x + y)
|
||||||
raise OSError('out of pty devices')
|
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():
|
def fork():
|
||||||
"""fork() -> (pid, master_fd)
|
"""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.
|
# as part of the release process.
|
||||||
|
|
||||||
module_docs = {
|
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:
|
if not c:
|
||||||
return x
|
return x
|
||||||
while True:
|
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:
|
if y > n:
|
||||||
return x
|
return x
|
||||||
x += 1
|
x += 1
|
||||||
@@ -844,8 +848,8 @@ class Random(_random.Random):
|
|||||||
# BTRS: Transformed rejection with squeeze method by Wolfgang Hörmann
|
# 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
|
# 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
|
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
|
spq = _sqrt(n * p * (1.0 - p)) # Standard deviation of the distribution
|
||||||
b = 1.15 + 2.53 * spq
|
b = 1.15 + 2.53 * spq
|
||||||
a = -0.0873 + 0.0248 * b + 0.01 * p
|
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)
|
k = _floor((2.0 * a / us + b) * u + c)
|
||||||
if k < 0 or k > n:
|
if k < 0 or k > n:
|
||||||
continue
|
continue
|
||||||
|
v = random()
|
||||||
|
|
||||||
# The early-out "squeeze" test substantially reduces
|
# The early-out "squeeze" test substantially reduces
|
||||||
# the number of acceptance condition evaluations.
|
# the number of acceptance condition evaluations.
|
||||||
v = random()
|
|
||||||
if us >= 0.07 and v <= vr:
|
if us >= 0.07 and v <= vr:
|
||||||
return k
|
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:
|
if not setup_complete:
|
||||||
alpha = (2.83 + 5.1 / b) * spq
|
alpha = (2.83 + 5.1 / b) * spq
|
||||||
lpq = _log(p / (1.0 - p))
|
lpq = _log(p / (1.0 - p))
|
||||||
m = _floor((n + 1) * p) # Mode of the distribution
|
m = _floor((n + 1) * p) # Mode of the distribution
|
||||||
h = _lgamma(m + 1) + _lgamma(n - m + 1)
|
h = _lgamma(m + 1) + _lgamma(n - m + 1)
|
||||||
setup_complete = True # Only needs to be done once
|
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)
|
v *= alpha / (a / (us * us) + b)
|
||||||
if _log(v) <= h - _lgamma(k + 1) - _lgamma(n - k + 1) + (k - m) * lpq:
|
if _log(v) <= h - _lgamma(k + 1) - _lgamma(n - k + 1) + (k - m) * lpq:
|
||||||
return k
|
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
|
# Helper to get the full name, spec and code for a module
|
||||||
def _get_module_details(mod_name, error=ImportError):
|
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("."):
|
if mod_name.startswith("."):
|
||||||
raise error("Relative module names not supported")
|
raise error("Relative module names not supported", **kwargs)
|
||||||
pkg_name, _, _ = mod_name.rpartition(".")
|
pkg_name, _, _ = mod_name.rpartition(".")
|
||||||
if pkg_name:
|
if pkg_name:
|
||||||
# Try importing the parent to avoid catching initialization errors
|
# 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"):
|
if mod_name.endswith(".py"):
|
||||||
msg += (f". Try using '{mod_name[:-3]}' instead of "
|
msg += (f". Try using '{mod_name[:-3]}' instead of "
|
||||||
f"'{mod_name}' as the module name.")
|
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:
|
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 spec.submodule_search_locations is not None:
|
||||||
if mod_name == "__main__" or mod_name.endswith(".__main__"):
|
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:
|
try:
|
||||||
pkg_main_name = mod_name + ".__main__"
|
pkg_main_name = mod_name + ".__main__"
|
||||||
return _get_module_details(pkg_main_name, error)
|
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:
|
if mod_name not in sys.modules:
|
||||||
raise # No module loaded; being a package is irrelevant
|
raise # No module loaded; being a package is irrelevant
|
||||||
raise error(("%s; %r is a package and cannot " +
|
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
|
loader = spec.loader
|
||||||
if loader is None:
|
if loader is None:
|
||||||
raise error("%r is a namespace package and cannot be executed"
|
raise error("%r is a namespace package and cannot be executed"
|
||||||
% mod_name)
|
% mod_name,
|
||||||
|
**kwargs)
|
||||||
try:
|
try:
|
||||||
code = loader.get_code(mod_name)
|
code = loader.get_code(mod_name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
raise error(format(e)) from e
|
raise error(format(e), **kwargs) from e
|
||||||
if code is None:
|
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
|
return mod_name, spec, code
|
||||||
|
|
||||||
class _Error(Exception):
|
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
|
# Also moves the standard __main__ out of the way so that the
|
||||||
# preexisting __loader__ entry doesn't cause issues
|
# preexisting __loader__ entry doesn't cause issues
|
||||||
main_name = "__main__"
|
main_name = "__main__"
|
||||||
|
kwargs = {"name": main_name} if issubclass(error, ImportError) else {}
|
||||||
saved_main = sys.modules[main_name]
|
saved_main = sys.modules[main_name]
|
||||||
del sys.modules[main_name]
|
del sys.modules[main_name]
|
||||||
try:
|
try:
|
||||||
@@ -241,7 +247,8 @@ def _get_main_module_details(error=ImportError):
|
|||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
if main_name in str(exc):
|
if main_name in str(exc):
|
||||||
raise error("can't find %r module in %r" %
|
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
|
raise
|
||||||
finally:
|
finally:
|
||||||
sys.modules[main_name] = saved_main
|
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):
|
if not zipfile.is_zipfile(filename):
|
||||||
raise ReadError("%s is not a zip file" % filename)
|
raise ReadError("%s is not a zip file" % filename)
|
||||||
|
|
||||||
zip = zipfile.ZipFile(filename)
|
with zipfile.ZipFile(filename) as zip:
|
||||||
try:
|
zip._ignore_invalid_names = True
|
||||||
for info in zip.infolist():
|
zip.extractall(extract_dir)
|
||||||
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()
|
|
||||||
|
|
||||||
def _unpack_tarfile(filename, extract_dir, *, filter=None):
|
def _unpack_tarfile(filename, extract_dir, *, filter=None):
|
||||||
"""Unpack tar/tar.gz/tar.bz2/tar.xz/tar.zst `filename` to `extract_dir`
|
"""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()
|
# Copy of sysconfig._get_implementation()
|
||||||
def _get_implementation():
|
def _get_implementation():
|
||||||
return 'RustPython' # XXX: RustPython; for site-packages
|
return 'RustPython' # XXX: RUSTPYTHON; for site-packages
|
||||||
|
|
||||||
# Copy of sysconfig._getuserbase()
|
# Copy of sysconfig._getuserbase()
|
||||||
def _getuserbase():
|
def _getuserbase():
|
||||||
|
|||||||
2
Lib/smtplib.py
vendored
2
Lib/smtplib.py
vendored
@@ -251,7 +251,6 @@ class SMTP:
|
|||||||
will be used.
|
will be used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._host = host
|
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.esmtp_features = {}
|
self.esmtp_features = {}
|
||||||
self.command_encoding = 'ascii'
|
self.command_encoding = 'ascii'
|
||||||
@@ -342,6 +341,7 @@ class SMTP:
|
|||||||
port = int(port)
|
port = int(port)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise OSError("nonnumeric port")
|
raise OSError("nonnumeric port")
|
||||||
|
self._host = host
|
||||||
if not port:
|
if not port:
|
||||||
port = self.default_port
|
port = self.default_port
|
||||||
sys.audit("smtplib.connect", self, host, 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
|
# Authenticating avoids using a connection from something else
|
||||||
# able to connect to {host}:{port} instead of us.
|
# able to connect to {host}:{port} instead of us.
|
||||||
# We expect only AF_INET and AF_INET6 families.
|
# We expect only AF_INET and AF_INET6 families.
|
||||||
try:
|
#
|
||||||
if (
|
# Note that we skip this on WASI because on that platorm the client socket
|
||||||
ssock.getsockname() != csock.getpeername()
|
# may not have finished connecting by the time we've reached this point (gh-146139).
|
||||||
or csock.getsockname() != ssock.getpeername()
|
if sys.platform != "wasi":
|
||||||
):
|
try:
|
||||||
raise ConnectionError("Unexpected peer connection")
|
if (
|
||||||
except:
|
ssock.getsockname() != csock.getpeername()
|
||||||
# getsockname() and getpeername() can fail
|
or csock.getsockname() != ssock.getpeername()
|
||||||
# if either socket isn't connected.
|
):
|
||||||
ssock.close()
|
raise ConnectionError("Unexpected peer connection")
|
||||||
csock.close()
|
except:
|
||||||
raise
|
# getsockname() and getpeername() can fail
|
||||||
|
# if either socket isn't connected.
|
||||||
|
ssock.close()
|
||||||
|
csock.close()
|
||||||
|
raise
|
||||||
|
|
||||||
return (ssock, csock)
|
return (ssock, csock)
|
||||||
|
|
||||||
|
|||||||
19
Lib/subprocess.py
vendored
19
Lib/subprocess.py
vendored
@@ -351,15 +351,16 @@ def _args_from_interpreter_flags():
|
|||||||
# -X options
|
# -X options
|
||||||
if dev_mode:
|
if dev_mode:
|
||||||
args.extend(('-X', 'dev'))
|
args.extend(('-X', 'dev'))
|
||||||
for opt in ('faulthandler', 'tracemalloc', 'importtime',
|
for opt in sorted(xoptions):
|
||||||
'frozen_modules', 'showrefcount', 'utf8', 'gil'):
|
if opt == 'dev':
|
||||||
if opt in xoptions:
|
# handled above via sys.flags.dev_mode
|
||||||
value = xoptions[opt]
|
continue
|
||||||
if value is True:
|
value = xoptions[opt]
|
||||||
arg = opt
|
if value is True:
|
||||||
else:
|
arg = opt
|
||||||
arg = '%s=%s' % (opt, value)
|
else:
|
||||||
args.extend(('-X', arg))
|
arg = '%s=%s' % (opt, value)
|
||||||
|
args.extend(('-X', arg))
|
||||||
|
|
||||||
return args
|
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']
|
_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']
|
||||||
|
|
||||||
def _get_implementation():
|
def _get_implementation():
|
||||||
return 'RustPython' # XXX: For site-packages
|
return 'RustPython' # XXX: RUSTPYTHON; For site-packages
|
||||||
|
|
||||||
# NOTE: site.py has copy of this function.
|
# NOTE: site.py has copy of this function.
|
||||||
# Sync it when modify this function.
|
# Sync it when modify this function.
|
||||||
@@ -698,11 +698,19 @@ def get_platform():
|
|||||||
release = get_config_var("ANDROID_API_LEVEL")
|
release = get_config_var("ANDROID_API_LEVEL")
|
||||||
|
|
||||||
# Wheel tags use the ABI names from Android's own tools.
|
# 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 = {
|
machine = {
|
||||||
"x86_64": "x86_64",
|
|
||||||
"i686": "x86",
|
|
||||||
"aarch64": "arm64_v8a",
|
"aarch64": "arm64_v8a",
|
||||||
|
"arm": "armeabi_v7a",
|
||||||
"armv7l": "armeabi_v7a",
|
"armv7l": "armeabi_v7a",
|
||||||
|
"armv8l": "armeabi_v7a",
|
||||||
|
"i686": "x86",
|
||||||
|
"x86_64": "x86_64",
|
||||||
}[machine]
|
}[machine]
|
||||||
elif osname == "linux":
|
elif osname == "linux":
|
||||||
# At least on Linux/Intel, 'machine' is the processor --
|
# 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
|
@classmethod
|
||||||
def frombuf(cls, buf, encoding, errors):
|
def frombuf(cls, buf, encoding, errors):
|
||||||
"""Construct a TarInfo object from a 512 byte bytes object.
|
"""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:
|
if len(buf) == 0:
|
||||||
raise EmptyHeaderError("empty header")
|
raise EmptyHeaderError("empty header")
|
||||||
@@ -1308,7 +1322,7 @@ class TarInfo(object):
|
|||||||
|
|
||||||
# Old V7 tar format represents a directory as a regular
|
# Old V7 tar format represents a directory as a regular
|
||||||
# file with a trailing slash.
|
# 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
|
obj.type = DIRTYPE
|
||||||
|
|
||||||
# The old GNU sparse format occupies some of the unused
|
# 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
|
"""Return the next TarInfo object from TarFile object
|
||||||
tarfile.
|
tarfile.
|
||||||
"""
|
"""
|
||||||
|
return cls._fromtarfile(tarfile)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fromtarfile(cls, tarfile, *, dircheck=True):
|
||||||
|
"""
|
||||||
|
See dircheck documentation in _frombuf().
|
||||||
|
"""
|
||||||
buf = tarfile.fileobj.read(BLOCKSIZE)
|
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
|
obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
|
||||||
return obj._proc_member(tarfile)
|
return obj._proc_member(tarfile)
|
||||||
|
|
||||||
@@ -1402,7 +1423,7 @@ class TarInfo(object):
|
|||||||
|
|
||||||
# Fetch the next header and process it.
|
# Fetch the next header and process it.
|
||||||
try:
|
try:
|
||||||
next = self.fromtarfile(tarfile)
|
next = self._fromtarfile(tarfile, dircheck=False)
|
||||||
except HeaderError as e:
|
except HeaderError as e:
|
||||||
raise SubsequentHeaderError(str(e)) from None
|
raise SubsequentHeaderError(str(e)) from None
|
||||||
|
|
||||||
@@ -1537,7 +1558,7 @@ class TarInfo(object):
|
|||||||
|
|
||||||
# Fetch the next header.
|
# Fetch the next header.
|
||||||
try:
|
try:
|
||||||
next = self.fromtarfile(tarfile)
|
next = self._fromtarfile(tarfile, dircheck=False)
|
||||||
except HeaderError as e:
|
except HeaderError as e:
|
||||||
raise SubsequentHeaderError(str(e)) from None
|
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'):
|
if hasattr(_os, 'O_BINARY'):
|
||||||
_bin_openflags |= _os.O_BINARY
|
_bin_openflags |= _os.O_BINARY
|
||||||
|
|
||||||
if hasattr(_os, 'TMP_MAX'):
|
# This is more than enough.
|
||||||
TMP_MAX = _os.TMP_MAX
|
# Each name contains over 40 random bits. Even with a million temporary
|
||||||
else:
|
# files, the chance of a conflict is less than 1 in a million, and with
|
||||||
TMP_MAX = 10000
|
# 20 attempts, it is less than 1e-120.
|
||||||
|
TMP_MAX = 20
|
||||||
|
|
||||||
# This variable _was_ unused for legacy reasons, see issue 10354.
|
# 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
|
# 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:
|
for dir in dirlist:
|
||||||
if dir != _os.curdir:
|
if dir != _os.curdir:
|
||||||
dir = _os.path.abspath(dir)
|
dir = _os.path.abspath(dir)
|
||||||
# Try only a few names per directory.
|
for seq in range(TMP_MAX):
|
||||||
for seq in range(100):
|
|
||||||
name = next(namer)
|
name = next(namer)
|
||||||
filename = _os.path.join(dir, name)
|
filename = _os.path.join(dir, name)
|
||||||
try:
|
try:
|
||||||
@@ -213,10 +213,8 @@ def _get_default_tempdir(dirlist=None):
|
|||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
pass
|
pass
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
# This exception is thrown when a directory with the chosen name
|
# See the comment in mkdtemp().
|
||||||
# already exists on windows.
|
if _os.name == 'nt' and _os.path.isdir(dir):
|
||||||
if (_os.name == 'nt' and _os.path.isdir(dir) and
|
|
||||||
_os.access(dir, _os.W_OK)):
|
|
||||||
continue
|
continue
|
||||||
break # no point trying more names in this directory
|
break # no point trying more names in this directory
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -258,10 +256,8 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type):
|
|||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
continue # try again
|
continue # try again
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
# This exception is thrown when a directory with the chosen name
|
# See the comment in mkdtemp().
|
||||||
# already exists on windows.
|
if _os.name == 'nt' and _os.path.isdir(dir) and seq < TMP_MAX - 1:
|
||||||
if (_os.name == 'nt' and _os.path.isdir(dir) and
|
|
||||||
_os.access(dir, _os.W_OK)):
|
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
@@ -386,10 +382,14 @@ def mkdtemp(suffix=None, prefix=None, dir=None):
|
|||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
continue # try again
|
continue # try again
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
# This exception is thrown when a directory with the chosen name
|
# On Posix, this exception is raised when the user has no
|
||||||
# already exists on windows.
|
# write access to the parent directory.
|
||||||
if (_os.name == 'nt' and _os.path.isdir(dir) and
|
# On Windows, it is also raised when a directory with
|
||||||
_os.access(dir, _os.W_OK)):
|
# 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
|
continue
|
||||||
else:
|
else:
|
||||||
raise
|
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
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")
|
||||||
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:
|
try:
|
||||||
import _pydatetime
|
import _pydatetime
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
_pydatetime = None
|
||||||
|
try:
|
||||||
|
import _datetime
|
||||||
|
except ImportError:
|
||||||
|
_datetime = None
|
||||||
#
|
#
|
||||||
|
|
||||||
pickle_loads = {pickle.loads, pickle._loads}
|
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"), "-0200" + z)
|
||||||
self.assertEqual(t.strftime("%:z"), "-02:00:" + z)
|
self.assertEqual(t.strftime("%:z"), "-02:00:" + z)
|
||||||
|
|
||||||
@unittest.skip("TODO: RUSTPYTHON")
|
|
||||||
def test_strftime_special(self):
|
def test_strftime_special(self):
|
||||||
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
|
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
|
||||||
s1 = t.strftime('%c')
|
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.
|
# gh-85432: The parameter was named "fmt" in the pure-Python impl.
|
||||||
t.strftime(format="%f")
|
t.strftime(format="%f")
|
||||||
|
|
||||||
@unittest.skip("TODO: RUSTPYTHON")
|
|
||||||
def test_strftime_special(self):
|
def test_strftime_special(self):
|
||||||
t = self.theclass(1, 2, 3, 4)
|
t = self.theclass(1, 2, 3, 4)
|
||||||
s1 = t.strftime('%I%p%Z')
|
s1 = t.strftime('%I%p%Z')
|
||||||
@@ -4360,7 +4362,6 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
|
|||||||
self.assertEqual(t.microsecond, 0)
|
self.assertEqual(t.microsecond, 0)
|
||||||
self.assertIsNone(t.tzinfo)
|
self.assertIsNone(t.tzinfo)
|
||||||
|
|
||||||
@unittest.skip("TODO: RUSTPYTHON")
|
|
||||||
def test_zones(self):
|
def test_zones(self):
|
||||||
est = FixedOffset(-300, "EST", 1)
|
est = FixedOffset(-300, "EST", 1)
|
||||||
utc = FixedOffset(0, "UTC", -2)
|
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
|
├── ReferenceError
|
||||||
├── RuntimeError
|
├── RuntimeError
|
||||||
│ ├── NotImplementedError
|
│ ├── NotImplementedError
|
||||||
|
│ ├── PythonFinalizationError
|
||||||
│ └── RecursionError
|
│ └── RecursionError
|
||||||
├── StopAsyncIteration
|
├── StopAsyncIteration
|
||||||
├── StopIteration
|
├── 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.
2
Lib/test/re_tests.py
vendored
2
Lib/test/re_tests.py
vendored
@@ -531,7 +531,7 @@ xyzabc
|
|||||||
(r'a[ ]*?\ (\d+).*', 'a 10', SUCCEED, 'found', 'a 10'),
|
(r'a[ ]*?\ (\d+).*', 'a 10', SUCCEED, 'found', 'a 10'),
|
||||||
(r'a[ ]*?\ (\d+).*', 'a 10', SUCCEED, 'found', 'a 10'),
|
(r'a[ ]*?\ (\d+).*', 'a 10', SUCCEED, 'found', 'a 10'),
|
||||||
# bug 127259: \Z shouldn't depend on multiline mode
|
# bug 127259: \Z shouldn't depend on multiline mode
|
||||||
(r'(?ms).*?x\s*\Z(.*)','xx\nx\n', SUCCEED, 'g1', ''),
|
(r'(?ms).*?x\s*\z(.*)','xx\nx\n', SUCCEED, 'g1', ''),
|
||||||
# bug 128899: uppercase literals under the ignorecase flag
|
# bug 128899: uppercase literals under the ignorecase flag
|
||||||
(r'(?i)M+', 'MMM', SUCCEED, 'found', 'MMM'),
|
(r'(?i)M+', 'MMM', SUCCEED, 'found', 'MMM'),
|
||||||
(r'(?i)m+', 'MMM', SUCCEED, 'found', 'MMM'),
|
(r'(?i)m+', 'MMM', SUCCEED, 'found', 'MMM'),
|
||||||
|
|||||||
33
Lib/test/seq_tests.py
vendored
33
Lib/test/seq_tests.py
vendored
@@ -261,23 +261,20 @@ class CommonTest(unittest.TestCase):
|
|||||||
self.assertEqual(min(u), 0)
|
self.assertEqual(min(u), 0)
|
||||||
self.assertEqual(max(u), 2)
|
self.assertEqual(max(u), 2)
|
||||||
|
|
||||||
def test_addmul(self):
|
def test_add(self):
|
||||||
u1 = self.type2test([0])
|
u1 = self.type2test([0])
|
||||||
u2 = self.type2test([0, 1])
|
u2 = self.type2test([0, 1])
|
||||||
self.assertEqual(u1, u1 + self.type2test())
|
self.assertEqual(u1, u1 + self.type2test())
|
||||||
self.assertEqual(u1, self.type2test() + u1)
|
self.assertEqual(u1, self.type2test() + u1)
|
||||||
self.assertEqual(u1 + self.type2test([1]), u2)
|
self.assertEqual(u1 + self.type2test([1]), u2)
|
||||||
self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0]))
|
self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0]))
|
||||||
self.assertEqual(self.type2test(), u2*0)
|
|
||||||
self.assertEqual(self.type2test(), 0*u2)
|
def test_mul(self):
|
||||||
|
u2 = self.type2test([0, 1])
|
||||||
self.assertEqual(self.type2test(), u2*0)
|
self.assertEqual(self.type2test(), u2*0)
|
||||||
self.assertEqual(self.type2test(), 0*u2)
|
self.assertEqual(self.type2test(), 0*u2)
|
||||||
self.assertEqual(u2, u2*1)
|
self.assertEqual(u2, u2*1)
|
||||||
self.assertEqual(u2, 1*u2)
|
self.assertEqual(u2, 1*u2)
|
||||||
self.assertEqual(u2, u2*1)
|
|
||||||
self.assertEqual(u2, 1*u2)
|
|
||||||
self.assertEqual(u2+u2, u2*2)
|
|
||||||
self.assertEqual(u2+u2, 2*u2)
|
|
||||||
self.assertEqual(u2+u2, u2*2)
|
self.assertEqual(u2+u2, u2*2)
|
||||||
self.assertEqual(u2+u2, 2*u2)
|
self.assertEqual(u2+u2, 2*u2)
|
||||||
self.assertEqual(u2+u2+u2, u2*3)
|
self.assertEqual(u2+u2+u2, u2*3)
|
||||||
@@ -286,8 +283,9 @@ class CommonTest(unittest.TestCase):
|
|||||||
class subclass(self.type2test):
|
class subclass(self.type2test):
|
||||||
pass
|
pass
|
||||||
u3 = subclass([0, 1])
|
u3 = subclass([0, 1])
|
||||||
self.assertEqual(u3, u3*1)
|
r = u3*1
|
||||||
self.assertIsNot(u3, u3*1)
|
self.assertEqual(r, u3)
|
||||||
|
self.assertIsNot(r, u3)
|
||||||
|
|
||||||
def test_iadd(self):
|
def test_iadd(self):
|
||||||
u = self.type2test([0, 1])
|
u = self.type2test([0, 1])
|
||||||
@@ -348,6 +346,21 @@ class CommonTest(unittest.TestCase):
|
|||||||
self.assertRaises(ValueError, a.__getitem__, slice(0, 10, 0))
|
self.assertRaises(ValueError, a.__getitem__, slice(0, 10, 0))
|
||||||
self.assertRaises(TypeError, a.__getitem__, 'x')
|
self.assertRaises(TypeError, a.__getitem__, 'x')
|
||||||
|
|
||||||
|
def _assert_cmp(self, a, b, r):
|
||||||
|
self.assertIs(a == b, r == 0)
|
||||||
|
self.assertIs(a != b, r != 0)
|
||||||
|
self.assertIs(a > b, r > 0)
|
||||||
|
self.assertIs(a <= b, r <= 0)
|
||||||
|
self.assertIs(a < b, r < 0)
|
||||||
|
self.assertIs(a >= b, r >= 0)
|
||||||
|
|
||||||
|
def test_cmp(self):
|
||||||
|
a = self.type2test([0, 1])
|
||||||
|
self._assert_cmp(a, a, 0)
|
||||||
|
self._assert_cmp(a, self.type2test([0, 1]), 0)
|
||||||
|
self._assert_cmp(a, self.type2test([0]), 1)
|
||||||
|
self._assert_cmp(a, self.type2test([0, 2]), -1)
|
||||||
|
|
||||||
def test_count(self):
|
def test_count(self):
|
||||||
a = self.type2test([0, 1, 2])*3
|
a = self.type2test([0, 1, 2])*3
|
||||||
self.assertEqual(a.count(0), 3)
|
self.assertEqual(a.count(0), 3)
|
||||||
@@ -426,7 +439,7 @@ class CommonTest(unittest.TestCase):
|
|||||||
self.assertEqual(lst2, lst)
|
self.assertEqual(lst2, lst)
|
||||||
self.assertNotEqual(id(lst2), id(lst))
|
self.assertNotEqual(id(lst2), id(lst))
|
||||||
|
|
||||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
@unittest.skip("TODO: RUSTPYTHON; hangs")
|
||||||
def test_free_after_iterating(self):
|
def test_free_after_iterating(self):
|
||||||
support.check_free_after_iterating(self, iter, self.type2test)
|
support.check_free_after_iterating(self, iter, self.type2test)
|
||||||
support.check_free_after_iterating(self, reversed, self.type2test)
|
support.check_free_after_iterating(self, reversed, self.type2test)
|
||||||
|
|||||||
138
Lib/test/string_tests.py
vendored
138
Lib/test/string_tests.py
vendored
@@ -90,6 +90,55 @@ class BaseTest:
|
|||||||
args = self.fixtype(args)
|
args = self.fixtype(args)
|
||||||
getattr(obj, methodname)(*args)
|
getattr(obj, methodname)(*args)
|
||||||
|
|
||||||
|
def _get_teststrings(self, charset, digits):
|
||||||
|
base = len(charset)
|
||||||
|
teststrings = set()
|
||||||
|
for i in range(base ** digits):
|
||||||
|
entry = []
|
||||||
|
for j in range(digits):
|
||||||
|
i, m = divmod(i, base)
|
||||||
|
entry.append(charset[m])
|
||||||
|
teststrings.add(''.join(entry))
|
||||||
|
teststrings = [self.fixtype(ts) for ts in teststrings]
|
||||||
|
return teststrings
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
s = self.fixtype('ab')
|
||||||
|
self.assertEqual(s + self.fixtype(''), s)
|
||||||
|
self.assertEqual(self.fixtype('') + s, s)
|
||||||
|
self.assertEqual(s + self.fixtype('cd'), self.fixtype('abcd'))
|
||||||
|
|
||||||
|
def test_mul(self):
|
||||||
|
s = self.fixtype('ab')
|
||||||
|
self.assertEqual(s*0, self.fixtype(''))
|
||||||
|
self.assertEqual(0*s, self.fixtype(''))
|
||||||
|
self.assertEqual(s*1, s)
|
||||||
|
self.assertEqual(1*s, s)
|
||||||
|
self.assertEqual(s*2, self.fixtype('abab'))
|
||||||
|
self.assertEqual(2*s, self.fixtype('abab'))
|
||||||
|
|
||||||
|
class subclass(self.type2test):
|
||||||
|
pass
|
||||||
|
s = subclass(self.fixtype('ab'))
|
||||||
|
r = s*1
|
||||||
|
self.assertEqual(r, s)
|
||||||
|
self.assertIsNot(r, s)
|
||||||
|
|
||||||
|
def _assert_cmp(self, a, b, r):
|
||||||
|
self.assertIs(a == b, r == 0)
|
||||||
|
self.assertIs(a != b, r != 0)
|
||||||
|
self.assertIs(a > b, r > 0)
|
||||||
|
self.assertIs(a <= b, r <= 0)
|
||||||
|
self.assertIs(a < b, r < 0)
|
||||||
|
self.assertIs(a >= b, r >= 0)
|
||||||
|
|
||||||
|
def test_cmp(self):
|
||||||
|
a = self.fixtype('ab')
|
||||||
|
self._assert_cmp(a, a, 0)
|
||||||
|
self._assert_cmp(a, self.fixtype('ab'), 0)
|
||||||
|
self._assert_cmp(a, self.fixtype('a'), 1)
|
||||||
|
self._assert_cmp(a, self.fixtype('ac'), -1)
|
||||||
|
|
||||||
def test_count(self):
|
def test_count(self):
|
||||||
self.checkequal(3, 'aaa', 'count', 'a')
|
self.checkequal(3, 'aaa', 'count', 'a')
|
||||||
self.checkequal(0, 'aaa', 'count', 'b')
|
self.checkequal(0, 'aaa', 'count', 'b')
|
||||||
@@ -130,17 +179,7 @@ class BaseTest:
|
|||||||
# For a variety of combinations,
|
# For a variety of combinations,
|
||||||
# verify that str.count() matches an equivalent function
|
# verify that str.count() matches an equivalent function
|
||||||
# replacing all occurrences and then differencing the string lengths
|
# replacing all occurrences and then differencing the string lengths
|
||||||
charset = ['', 'a', 'b']
|
teststrings = self._get_teststrings(['', 'a', 'b'], 7)
|
||||||
digits = 7
|
|
||||||
base = len(charset)
|
|
||||||
teststrings = set()
|
|
||||||
for i in range(base ** digits):
|
|
||||||
entry = []
|
|
||||||
for j in range(digits):
|
|
||||||
i, m = divmod(i, base)
|
|
||||||
entry.append(charset[m])
|
|
||||||
teststrings.add(''.join(entry))
|
|
||||||
teststrings = [self.fixtype(ts) for ts in teststrings]
|
|
||||||
for i in teststrings:
|
for i in teststrings:
|
||||||
n = len(i)
|
n = len(i)
|
||||||
for j in teststrings:
|
for j in teststrings:
|
||||||
@@ -197,17 +236,7 @@ class BaseTest:
|
|||||||
# For a variety of combinations,
|
# For a variety of combinations,
|
||||||
# verify that str.find() matches __contains__
|
# verify that str.find() matches __contains__
|
||||||
# and that the found substring is really at that location
|
# and that the found substring is really at that location
|
||||||
charset = ['', 'a', 'b', 'c']
|
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
|
||||||
digits = 5
|
|
||||||
base = len(charset)
|
|
||||||
teststrings = set()
|
|
||||||
for i in range(base ** digits):
|
|
||||||
entry = []
|
|
||||||
for j in range(digits):
|
|
||||||
i, m = divmod(i, base)
|
|
||||||
entry.append(charset[m])
|
|
||||||
teststrings.add(''.join(entry))
|
|
||||||
teststrings = [self.fixtype(ts) for ts in teststrings]
|
|
||||||
for i in teststrings:
|
for i in teststrings:
|
||||||
for j in teststrings:
|
for j in teststrings:
|
||||||
loc = i.find(j)
|
loc = i.find(j)
|
||||||
@@ -244,17 +273,7 @@ class BaseTest:
|
|||||||
# For a variety of combinations,
|
# For a variety of combinations,
|
||||||
# verify that str.rfind() matches __contains__
|
# verify that str.rfind() matches __contains__
|
||||||
# and that the found substring is really at that location
|
# and that the found substring is really at that location
|
||||||
charset = ['', 'a', 'b', 'c']
|
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
|
||||||
digits = 5
|
|
||||||
base = len(charset)
|
|
||||||
teststrings = set()
|
|
||||||
for i in range(base ** digits):
|
|
||||||
entry = []
|
|
||||||
for j in range(digits):
|
|
||||||
i, m = divmod(i, base)
|
|
||||||
entry.append(charset[m])
|
|
||||||
teststrings.add(''.join(entry))
|
|
||||||
teststrings = [self.fixtype(ts) for ts in teststrings]
|
|
||||||
for i in teststrings:
|
for i in teststrings:
|
||||||
for j in teststrings:
|
for j in teststrings:
|
||||||
loc = i.rfind(j)
|
loc = i.rfind(j)
|
||||||
@@ -295,6 +314,19 @@ class BaseTest:
|
|||||||
else:
|
else:
|
||||||
self.checkraises(TypeError, 'hello', 'index', 42)
|
self.checkraises(TypeError, 'hello', 'index', 42)
|
||||||
|
|
||||||
|
# For a variety of combinations,
|
||||||
|
# verify that str.index() matches __contains__
|
||||||
|
# and that the found substring is really at that location
|
||||||
|
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
|
||||||
|
for i in teststrings:
|
||||||
|
for j in teststrings:
|
||||||
|
if j in i:
|
||||||
|
loc = i.index(j)
|
||||||
|
self.assertGreaterEqual(loc, 0)
|
||||||
|
self.assertEqual(i[loc:loc+len(j)], j)
|
||||||
|
else:
|
||||||
|
self.assertRaises(ValueError, i.index, j)
|
||||||
|
|
||||||
def test_rindex(self):
|
def test_rindex(self):
|
||||||
self.checkequal(12, 'abcdefghiabc', 'rindex', '')
|
self.checkequal(12, 'abcdefghiabc', 'rindex', '')
|
||||||
self.checkequal(3, 'abcdefghiabc', 'rindex', 'def')
|
self.checkequal(3, 'abcdefghiabc', 'rindex', 'def')
|
||||||
@@ -321,6 +353,19 @@ class BaseTest:
|
|||||||
else:
|
else:
|
||||||
self.checkraises(TypeError, 'hello', 'rindex', 42)
|
self.checkraises(TypeError, 'hello', 'rindex', 42)
|
||||||
|
|
||||||
|
# For a variety of combinations,
|
||||||
|
# verify that str.rindex() matches __contains__
|
||||||
|
# and that the found substring is really at that location
|
||||||
|
teststrings = self._get_teststrings(['', 'a', 'b', 'c'], 5)
|
||||||
|
for i in teststrings:
|
||||||
|
for j in teststrings:
|
||||||
|
if j in i:
|
||||||
|
loc = i.rindex(j)
|
||||||
|
self.assertGreaterEqual(loc, 0)
|
||||||
|
self.assertEqual(i[loc:loc+len(j)], j)
|
||||||
|
else:
|
||||||
|
self.assertRaises(ValueError, i.rindex, j)
|
||||||
|
|
||||||
def test_find_periodic_pattern(self):
|
def test_find_periodic_pattern(self):
|
||||||
"""Cover the special path for periodic patterns."""
|
"""Cover the special path for periodic patterns."""
|
||||||
def reference_find(p, s):
|
def reference_find(p, s):
|
||||||
@@ -437,8 +482,10 @@ class BaseTest:
|
|||||||
self.checkequal(' a\n b', ' \ta\n\tb', 'expandtabs', 1)
|
self.checkequal(' a\n b', ' \ta\n\tb', 'expandtabs', 1)
|
||||||
|
|
||||||
self.checkraises(TypeError, 'hello', 'expandtabs', 42, 42)
|
self.checkraises(TypeError, 'hello', 'expandtabs', 42, 42)
|
||||||
|
# TODO: RUSTPYTHON; expandtabs overflow checks
|
||||||
|
# if sys.maxsize < (1 << 32) and struct.calcsize('P') == 4:
|
||||||
|
#
|
||||||
# This test is only valid when sizeof(int) == sizeof(void*) == 4.
|
# This test is only valid when sizeof(int) == sizeof(void*) == 4.
|
||||||
# XXX RUSTPYTHON TODO: expandtabs overflow checks
|
|
||||||
if sys.maxsize < (1 << 32) and struct.calcsize('P') == 4 and False:
|
if sys.maxsize < (1 << 32) and struct.calcsize('P') == 4 and False:
|
||||||
self.checkraises(OverflowError,
|
self.checkraises(OverflowError,
|
||||||
'\ta\n\tb', 'expandtabs', sys.maxsize)
|
'\ta\n\tb', 'expandtabs', sys.maxsize)
|
||||||
@@ -768,6 +815,15 @@ class BaseTest:
|
|||||||
self.checkraises(TypeError, 'hello', 'replace', 42, 'h')
|
self.checkraises(TypeError, 'hello', 'replace', 42, 'h')
|
||||||
self.checkraises(TypeError, 'hello', 'replace', 'h', 42)
|
self.checkraises(TypeError, 'hello', 'replace', 'h', 42)
|
||||||
|
|
||||||
|
def test_replacement_on_buffer_boundary(self):
|
||||||
|
# gh-127971: Check we don't read past the end of the buffer when a
|
||||||
|
# potential match misses on the last character.
|
||||||
|
any_3_nonblank_codepoints = '!!!'
|
||||||
|
seven_codepoints = any_3_nonblank_codepoints + ' ' + any_3_nonblank_codepoints
|
||||||
|
a = (' ' * 243) + seven_codepoints + (' ' * 7)
|
||||||
|
b = ' ' * 6 + chr(256)
|
||||||
|
a.replace(seven_codepoints, b)
|
||||||
|
|
||||||
def test_replace_uses_two_way_maxcount(self):
|
def test_replace_uses_two_way_maxcount(self):
|
||||||
# Test that maxcount works in _two_way_count in fastsearch.h
|
# Test that maxcount works in _two_way_count in fastsearch.h
|
||||||
A, B = "A"*1000, "B"*1000
|
A, B = "A"*1000, "B"*1000
|
||||||
@@ -780,7 +836,7 @@ class BaseTest:
|
|||||||
self.checkequal(AABAA + "ccc",
|
self.checkequal(AABAA + "ccc",
|
||||||
AABAA + ABBA, 'replace', ABBA, "ccc", 2)
|
AABAA + ABBA, 'replace', ABBA, "ccc", 2)
|
||||||
|
|
||||||
@unittest.skip("TODO: RUSTPYTHON, may only apply to 32-bit platforms")
|
@unittest.skip("TODO: RUSTPYTHON; may only apply to 32-bit platforms")
|
||||||
@unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4,
|
@unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4,
|
||||||
'only applies to 32-bit platforms')
|
'only applies to 32-bit platforms')
|
||||||
def test_replace_overflow(self):
|
def test_replace_overflow(self):
|
||||||
@@ -1134,8 +1190,8 @@ class StringLikeTest(BaseTest):
|
|||||||
self.checkequal('\u2160\u2171\u2172',
|
self.checkequal('\u2160\u2171\u2172',
|
||||||
'\u2170\u2171\u2172', 'capitalize')
|
'\u2170\u2171\u2172', 'capitalize')
|
||||||
# check with Ll chars with no upper - nothing changes here
|
# check with Ll chars with no upper - nothing changes here
|
||||||
self.checkequal('\u019b\u1d00\u1d86\u0221\u1fb7',
|
self.checkequal('\u1d00\u1d86\u0221\u1fb7',
|
||||||
'\u019b\u1d00\u1d86\u0221\u1fb7', 'capitalize')
|
'\u1d00\u1d86\u0221\u1fb7', 'capitalize')
|
||||||
|
|
||||||
def test_startswith(self):
|
def test_startswith(self):
|
||||||
self.checkequal(True, 'hello', 'startswith', 'he')
|
self.checkequal(True, 'hello', 'startswith', 'he')
|
||||||
@@ -1248,9 +1304,7 @@ class StringLikeTest(BaseTest):
|
|||||||
self.checkequal(False, 'asd', '__contains__', 'asdf')
|
self.checkequal(False, 'asd', '__contains__', 'asdf')
|
||||||
self.checkequal(False, '', '__contains__', 'asdf')
|
self.checkequal(False, '', '__contains__', 'asdf')
|
||||||
|
|
||||||
|
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||||
# TODO: RUSTPYTHON
|
|
||||||
@unittest.expectedFailure
|
|
||||||
def test_subscript(self):
|
def test_subscript(self):
|
||||||
self.checkequal('a', 'abc', '__getitem__', 0)
|
self.checkequal('a', 'abc', '__getitem__', 0)
|
||||||
self.checkequal('c', 'abc', '__getitem__', -1)
|
self.checkequal('c', 'abc', '__getitem__', -1)
|
||||||
@@ -1292,6 +1346,7 @@ class StringLikeTest(BaseTest):
|
|||||||
slice(start, stop, step))
|
slice(start, stop, step))
|
||||||
|
|
||||||
def test_mul(self):
|
def test_mul(self):
|
||||||
|
super().test_mul()
|
||||||
self.checkequal('', 'abc', '__mul__', -1)
|
self.checkequal('', 'abc', '__mul__', -1)
|
||||||
self.checkequal('', 'abc', '__mul__', 0)
|
self.checkequal('', 'abc', '__mul__', 0)
|
||||||
self.checkequal('abc', 'abc', '__mul__', 1)
|
self.checkequal('abc', 'abc', '__mul__', 1)
|
||||||
@@ -1504,8 +1559,7 @@ class StringLikeTest(BaseTest):
|
|||||||
self.checkequal(True, s, 'startswith', 'h', None, -2)
|
self.checkequal(True, s, 'startswith', 'h', None, -2)
|
||||||
self.checkequal(False, s, 'startswith', 'x', None, None)
|
self.checkequal(False, s, 'startswith', 'x', None, None)
|
||||||
|
|
||||||
# TODO: RUSTPYTHON
|
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||||
@unittest.expectedFailure
|
|
||||||
def test_find_etc_raise_correct_error_messages(self):
|
def test_find_etc_raise_correct_error_messages(self):
|
||||||
# issue 11828
|
# issue 11828
|
||||||
s = 'hello'
|
s = 'hello'
|
||||||
|
|||||||
18
Lib/test/support/__init__.py
vendored
18
Lib/test/support/__init__.py
vendored
@@ -548,7 +548,6 @@ def requires_lzma(reason='requires lzma'):
|
|||||||
import lzma
|
import lzma
|
||||||
except ImportError:
|
except ImportError:
|
||||||
lzma = None
|
lzma = None
|
||||||
lzma = None # XXX: RUSTPYTHON; xz is not supported yet
|
|
||||||
return unittest.skipUnless(lzma, reason)
|
return unittest.skipUnless(lzma, reason)
|
||||||
|
|
||||||
def requires_zstd(reason='requires zstd'):
|
def requires_zstd(reason='requires zstd'):
|
||||||
@@ -856,8 +855,6 @@ def gc_collect():
|
|||||||
longer than expected. This function tries its best to force all garbage
|
longer than expected. This function tries its best to force all garbage
|
||||||
objects to disappear.
|
objects to disappear.
|
||||||
"""
|
"""
|
||||||
return # TODO: RUSTPYTHON
|
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
gc.collect()
|
gc.collect()
|
||||||
gc.collect()
|
gc.collect()
|
||||||
@@ -865,13 +862,6 @@ def gc_collect():
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def disable_gc():
|
def disable_gc():
|
||||||
# TODO: RUSTPYTHON; GC is not supported yet
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
have_gc = gc.isenabled()
|
have_gc = gc.isenabled()
|
||||||
gc.disable()
|
gc.disable()
|
||||||
@@ -2004,10 +1994,6 @@ def _check_tracemalloc():
|
|||||||
|
|
||||||
|
|
||||||
def check_free_after_iterating(test, iter, cls, args=()):
|
def check_free_after_iterating(test, iter, cls, args=()):
|
||||||
# TODO: RUSTPYTHON; GC is not supported yet
|
|
||||||
test.assertTrue(False)
|
|
||||||
return
|
|
||||||
|
|
||||||
done = False
|
done = False
|
||||||
def wrapper():
|
def wrapper():
|
||||||
class A(cls):
|
class A(cls):
|
||||||
@@ -3048,6 +3034,10 @@ def get_signal_name(exitcode):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Format Windows exit status as hexadecimal
|
||||||
|
if 0xC0000000 <= exitcode:
|
||||||
|
return f"0x{exitcode:X}"
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class BrokenIter:
|
class BrokenIter:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
from enum import Enum
|
||||||
import functools
|
import functools
|
||||||
import unittest
|
import unittest
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"given",
|
"given",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user