Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
354dab9503 | ||
|
|
ca2c732d66 | ||
|
|
2ec3d72151 | ||
|
|
02d43caa5e | ||
|
|
55c7e4eb49 | ||
|
|
7d160f96f6 | ||
|
|
1ccc63e83d | ||
|
|
971913be35 | ||
|
|
36ea6b3285 | ||
|
|
85f45771f1 | ||
|
|
30e702de11 | ||
|
|
778442a972 | ||
|
|
4f34712858 | ||
|
|
d821451a64 | ||
|
|
92d96ac336 | ||
|
|
c64e33173a | ||
|
|
5d2aed34f4 | ||
|
|
04c1c0f871 | ||
|
|
0f128c6deb | ||
|
|
8373ef079f | ||
|
|
227cbde169 | ||
|
|
1f06e6f0c9 | ||
|
|
2d3b8ac8df | ||
|
|
fa6072b4fa | ||
|
|
aae2f7c49a | ||
|
|
52443daf12 | ||
|
|
86d57edda4 | ||
|
|
7298350e76 | ||
|
|
3184264b3b | ||
|
|
d4a1657b2e | ||
|
|
bea401912f | ||
|
|
3e4070bbb3 | ||
|
|
3d7ad50f57 | ||
|
|
85ec94cf65 | ||
|
|
0cc5c974f6 | ||
|
|
6f76bb945a | ||
|
|
239a3730a6 | ||
|
|
8740ff2691 | ||
|
|
4b5b2e791b | ||
|
|
b89f9445c2 | ||
|
|
ce6ddb91de | ||
|
|
fe60b5ca13 | ||
|
|
fa2d03a4fb | ||
|
|
1b16ca0e2e | ||
|
|
f9cec89038 | ||
|
|
bc4f6ed9dd | ||
|
|
fd435a7bbb | ||
|
|
ce0ed4b8ae | ||
|
|
42a759a7ae | ||
|
|
6dcae857a7 | ||
|
|
34792c0077 | ||
|
|
6df463b1e3 | ||
|
|
4740178bdf | ||
|
|
06d6776422 | ||
|
|
1095820006 | ||
|
|
47e60107b2 | ||
|
|
12a2d10595 | ||
|
|
ccdf7eddf4 | ||
|
|
83d4ec9e84 | ||
|
|
69750b9bf0 | ||
|
|
a03a093e2c | ||
|
|
6094e7b39a | ||
|
|
98d0a55a02 | ||
|
|
6eabde1519 | ||
|
|
a4f45993f8 | ||
|
|
49e35497ae | ||
|
|
bf310c780c | ||
|
|
e671a0cb6d | ||
|
|
a5a36a049c | ||
|
|
43ff2833f3 | ||
|
|
5f2f4af851 | ||
|
|
bf56103428 | ||
|
|
78e87d0ab8 | ||
|
|
04572422d4 | ||
|
|
cb55b45d47 | ||
|
|
e3fedb52f1 | ||
|
|
c1f4e7d874 | ||
|
|
4082728c32 | ||
|
|
66e2a0fce4 | ||
|
|
39fe9a1979 | ||
|
|
f18493b627 | ||
|
|
fa2abb5ff6 | ||
|
|
5b81abd537 | ||
|
|
01b2e8e6b8 | ||
|
|
ff439039da | ||
|
|
216f976fd5 | ||
|
|
e617c9d344 | ||
|
|
58ef6cd36b | ||
|
|
abf63d73d3 | ||
|
|
76c0c373da | ||
|
|
209e2713fd | ||
|
|
f12ed2088a | ||
|
|
94bf83c826 | ||
|
|
cce3ecb1e4 | ||
|
|
10aa21f970 | ||
|
|
425287055b | ||
|
|
a9e2cd5a74 | ||
|
|
2f7d0ec42c | ||
|
|
20be4f02c8 | ||
|
|
6364dd1511 | ||
|
|
19b8eaea59 | ||
|
|
b892c07841 | ||
|
|
cefc363f64 | ||
|
|
45c90efb5c | ||
|
|
0571e12617 | ||
|
|
86e0e16625 | ||
|
|
b3edda30c4 | ||
|
|
9d1587a41d | ||
|
|
e593396417 | ||
|
|
a9cb9fbb1f | ||
|
|
3f74981d5e | ||
|
|
38f853cf86 | ||
|
|
4b7e837f0f | ||
|
|
a2d9fbcd42 | ||
|
|
6315709fea | ||
|
|
48e5aa777b | ||
|
|
69be26b16e | ||
|
|
5e509814f7 | ||
|
|
07fbcd2262 | ||
|
|
1c87ae378d | ||
|
|
b1f95b4bf9 | ||
|
|
7d9205d4ae | ||
|
|
9b3cd15c5f | ||
|
|
1418aada91 | ||
|
|
f98a9f7999 | ||
|
|
35c2d660cb | ||
|
|
c09e21ae4b | ||
|
|
480c352d33 | ||
|
|
8f167be980 | ||
|
|
e86afc1705 | ||
|
|
7423c65eb5 | ||
|
|
b651c6541a | ||
|
|
403564315c | ||
|
|
5e6138d16f | ||
|
|
6ef18bea50 | ||
|
|
9505dabfef | ||
|
|
4783c065da | ||
|
|
bb303a75c0 | ||
|
|
1e633ab8ed | ||
|
|
89f525407a | ||
|
|
d7d42c8e39 | ||
|
|
abb2250bf5 | ||
|
|
ae2becb531 | ||
|
|
14069e81fd | ||
|
|
401e8d3100 | ||
|
|
e7c2c85b9f | ||
|
|
784e965d3a | ||
|
|
10c4dbf785 | ||
|
|
7281302281 | ||
|
|
27603f9780 | ||
|
|
d9a90d5d5e | ||
|
|
9957565b37 | ||
|
|
5bc174bf8d | ||
|
|
89f070ea98 | ||
|
|
04d24acb5a | ||
|
|
3b7106ae71 | ||
|
|
74116fe2ea | ||
|
|
1233caebdc | ||
|
|
d78567f853 | ||
|
|
1ece7698c2 | ||
|
|
7851dff915 | ||
|
|
e4987b3e7a | ||
|
|
d1851c369c | ||
|
|
d63fbd8624 | ||
|
|
b0f664ec94 | ||
|
|
9957374508 | ||
|
|
7f3a7db7e6 | ||
|
|
3ffe8475b8 | ||
|
|
396d7df314 | ||
|
|
0c618a4456 | ||
|
|
c4c36d8e2e | ||
|
|
829dda3ee9 | ||
|
|
a16f52b9fb | ||
|
|
a49c062b35 | ||
|
|
da15e916de | ||
|
|
480a464179 | ||
|
|
db0e90763b | ||
|
|
92af043906 | ||
|
|
806ad06d6a | ||
|
|
dac1fb0a06 | ||
|
|
ec08506704 | ||
|
|
7bcb420a0a | ||
|
|
546b970240 | ||
|
|
24a1bbb3ca | ||
|
|
a0de93a638 | ||
|
|
216ce8726c | ||
|
|
0c1ba1b305 | ||
|
|
5ab351dc8f | ||
|
|
f1366b8a74 | ||
|
|
7f67324210 | ||
|
|
a51f667be8 | ||
|
|
f7b445353f | ||
|
|
46154a3ee7 | ||
|
|
0790f376ca | ||
|
|
332631434c | ||
|
|
8ee0c8593e | ||
|
|
5a912de937 | ||
|
|
ef96c4c66b | ||
|
|
155a1dd3fc | ||
|
|
65601b6532 | ||
|
|
6e4dfda727 | ||
|
|
1fbe0d8d2e | ||
|
|
6e184bca97 | ||
|
|
188fdcd34f | ||
|
|
f4e537fd72 | ||
|
|
cfd8eb23b8 | ||
|
|
57c7d61989 | ||
|
|
db28eee760 | ||
|
|
0cd6a8f5cc | ||
|
|
17343bb57c | ||
|
|
182e1e864c | ||
|
|
782c561e86 | ||
|
|
9838040ca3 | ||
|
|
eea35ffc31 | ||
|
|
b639e7fd11 | ||
|
|
2c1e591718 | ||
|
|
49e4b1a0f8 | ||
|
|
ebfc330e86 | ||
|
|
e597dcc8fd | ||
|
|
07fc1ef837 | ||
|
|
a25e192ff9 | ||
|
|
e6a748b1a7 | ||
|
|
227cf00638 | ||
|
|
a9d32fbc99 | ||
|
|
b5e0c1e9c6 | ||
|
|
2aec4678da | ||
|
|
f9a35ae42a | ||
|
|
0538a3e224 | ||
|
|
64516f1b45 | ||
|
|
1dc83115be | ||
|
|
c651109b9a | ||
|
|
1df98c5bd6 | ||
|
|
aab9fd2fbe | ||
|
|
4e2ca9e5fd | ||
|
|
395f7fc59e | ||
|
|
4c557d4050 | ||
|
|
86eb48a89b | ||
|
|
3262ad4350 | ||
|
|
fb34df3987 | ||
|
|
23db298e2f | ||
|
|
9d5acf3c53 | ||
|
|
7e295d05a1 | ||
|
|
50b85153ce | ||
|
|
0e5869b52f | ||
|
|
d67dfba7f5 | ||
|
|
a3a9ae1a26 | ||
|
|
e41b966283 | ||
|
|
4188526e2d | ||
|
|
804b0f0d06 | ||
|
|
7b15f1736c | ||
|
|
4846848a1e | ||
|
|
344fef1e2f | ||
|
|
bc23458164 | ||
|
|
9a54e583e7 | ||
|
|
59ccd2da93 | ||
|
|
737c1e5792 | ||
|
|
f72adf0cbc | ||
|
|
5184d0cb9c | ||
|
|
2d0258db1a | ||
|
|
f5e6a25542 | ||
|
|
bc5a8ddf87 | ||
|
|
eabddb9698 | ||
|
|
6fcdc44f3e | ||
|
|
0d1a45ddc1 | ||
|
|
f9183bbf64 | ||
|
|
7ec8454fc1 | ||
|
|
a3410f124a | ||
|
|
3488f6b61d | ||
|
|
3dad9cac6b | ||
|
|
7aa13e35e9 | ||
|
|
cf1b54cfe5 | ||
|
|
8669405a1c | ||
|
|
54775acc7a | ||
|
|
be184241fd | ||
|
|
61ad05d511 | ||
|
|
1872ad311b | ||
|
|
364d33119c | ||
|
|
1702a6340e | ||
|
|
4ab35cac7b | ||
|
|
21f1df18b6 | ||
|
|
8ea4c0589c | ||
|
|
1d1c0ec3af | ||
|
|
7e637c5e5e | ||
|
|
4f11f20e1d | ||
|
|
674e0114a5 | ||
|
|
1f082b69d2 | ||
|
|
9de5ab2037 | ||
|
|
3415ccbb73 | ||
|
|
b165596a6e | ||
|
|
089a4713e3 | ||
|
|
365d725bc1 | ||
|
|
7586900fd9 | ||
|
|
c4de9ae2d3 | ||
|
|
3a0a484fcb | ||
|
|
df7dd026d2 | ||
|
|
a2108de2c0 | ||
|
|
6d0d75c7d9 | ||
|
|
d4f0424ddc | ||
|
|
cd6113c2c3 | ||
|
|
c9a21adc5f | ||
|
|
9adcf3d233 | ||
|
|
34cc8e9ad7 | ||
|
|
cf923ec6de | ||
|
|
105c097fea | ||
|
|
574b790296 | ||
|
|
3870253b56 | ||
|
|
21a380f7cb | ||
|
|
404661f361 | ||
|
|
1e58f9a15c | ||
|
|
24236be3ce | ||
|
|
8705149619 | ||
|
|
782dedd439 | ||
|
|
f9bbccbc13 | ||
|
|
cecdaa98ae | ||
|
|
9980f760b1 | ||
|
|
5946a5cd8c | ||
|
|
32687474db | ||
|
|
98a8b6c76c | ||
|
|
ca08365a81 | ||
|
|
8239328e42 | ||
|
|
b9131c6070 | ||
|
|
1c342d36e5 | ||
|
|
2d672d2f28 | ||
|
|
c62cbd6654 | ||
|
|
c36904d983 | ||
|
|
669b70b2cd | ||
|
|
7459d67fee | ||
|
|
741097827d | ||
|
|
4ceca54138 | ||
|
|
860d5e8889 | ||
|
|
64d131060c | ||
|
|
b7cdec427e | ||
|
|
df66b35444 | ||
|
|
cd9f2f31ea | ||
|
|
b54d7433c7 | ||
|
|
855fb5a936 | ||
|
|
8fdf16b316 | ||
|
|
fa6194c0a9 | ||
|
|
2d17b81313 | ||
|
|
cb1730f628 | ||
|
|
d848047012 | ||
|
|
716beae455 | ||
|
|
d16355fcf2 | ||
|
|
cd3d40a3b8 | ||
|
|
b3fc10a6e4 | ||
|
|
09dbbc6361 | ||
|
|
f5ad561c51 | ||
|
|
0db50b521d | ||
|
|
9942a3d44c | ||
|
|
47637c147c | ||
|
|
2fb072532a | ||
|
|
70aa7fc917 | ||
|
|
384b2ad014 | ||
|
|
f2975f9a05 | ||
|
|
41c146a6f3 | ||
|
|
be594f1498 | ||
|
|
99eba4e0eb | ||
|
|
43806f8668 | ||
|
|
d4aa583e16 | ||
|
|
381fbeda6a | ||
|
|
3104443212 | ||
|
|
16e2b86bcf | ||
|
|
0caee73975 | ||
|
|
7f25e28d89 | ||
|
|
ce8473ee63 | ||
|
|
7ccee7d8fc | ||
|
|
7cd89cff6e | ||
|
|
e1c0d2c501 | ||
|
|
be7a1346ec | ||
|
|
6dbc5e783e | ||
|
|
1115cbd94d | ||
|
|
bf4ec2282f | ||
|
|
e6e43413ff | ||
|
|
e9a0c01af8 | ||
|
|
d0270b5e59 | ||
|
|
b57654aed3 | ||
|
|
78ad06612e | ||
|
|
434866558a | ||
|
|
42963a0e03 | ||
|
|
c2d1da09cb | ||
|
|
f362932ec5 | ||
|
|
3b48c76e4a | ||
|
|
d56f607f35 | ||
|
|
39a2cd8aa2 | ||
|
|
14ca8e6499 | ||
|
|
2a227dcc7a | ||
|
|
12090ce74b | ||
|
|
25973554e2 | ||
|
|
138c22afe9 | ||
|
|
d19535340c | ||
|
|
5fcbed721d | ||
|
|
812776b9ce | ||
|
|
d606ea6759 | ||
|
|
c314e1c36e | ||
|
|
8c7a883abd | ||
|
|
55facaaf3d | ||
|
|
17ca3a620f | ||
|
|
9836a1b347 | ||
|
|
8c3fd99009 | ||
|
|
4d9cb083ac | ||
|
|
612fd23777 | ||
|
|
dca505c884 | ||
|
|
7fd5c8af8f | ||
|
|
97fb95ec0c | ||
|
|
e6d8a955d2 | ||
|
|
a3a147f028 | ||
|
|
c761dded35 | ||
|
|
92623232c3 | ||
|
|
9b58bf6199 | ||
|
|
9d5eb28523 | ||
|
|
857e0fc00e | ||
|
|
bf8b58aeeb | ||
|
|
f6803bce2c | ||
|
|
6bff30fbbb | ||
|
|
6d927d502e | ||
|
|
2e1284f044 | ||
|
|
11eb4f8fde | ||
|
|
c19c13e2c6 | ||
|
|
891383f8dc | ||
|
|
ce1ac38cde | ||
|
|
df951733cd | ||
|
|
912fe68069 | ||
|
|
be82d5ff36 | ||
|
|
784f9ff081 | ||
|
|
0f39135ae5 | ||
|
|
94d374c9ce | ||
|
|
f3152a8c2b | ||
|
|
f3eaa418bb | ||
|
|
f9176578ea | ||
|
|
17eeb73767 | ||
|
|
7756792bba | ||
|
|
ba4e3393d3 | ||
|
|
1a89fafce4 | ||
|
|
df307b8eda | ||
|
|
d96887b102 | ||
|
|
89ad9a500b | ||
|
|
086728365c | ||
|
|
f9e36943d4 | ||
|
|
b5c1c6d414 | ||
|
|
df76b2462e | ||
|
|
343a10d491 | ||
|
|
72c52f5f15 | ||
|
|
cfa01d3ac5 | ||
|
|
f8e7447d35 | ||
|
|
2ac806e39f | ||
|
|
7ebbf9da44 | ||
|
|
1c4a2edbdb | ||
|
|
1d3082249f | ||
|
|
09950233e7 | ||
|
|
d48575c8c5 | ||
|
|
f8a0cf76c8 | ||
|
|
851e0d59f0 | ||
|
|
10b2de2c3f | ||
|
|
3718d62e24 | ||
|
|
a793dd7c91 | ||
|
|
0850b86456 | ||
|
|
f07dee3564 | ||
|
|
f871f5e726 | ||
|
|
803c3cb271 | ||
|
|
7ff76bb7d0 | ||
|
|
e7feeef64e | ||
|
|
8aaa8e0044 | ||
|
|
cbfbe7c08d | ||
|
|
81926a785c | ||
|
|
9ccb596f93 | ||
|
|
25db8de0da | ||
|
|
24dffe4226 | ||
|
|
c3fc129695 | ||
|
|
02c3d651bd | ||
|
|
bdd4deedc1 | ||
|
|
9d55194b92 | ||
|
|
102f83ea85 | ||
|
|
22902f6dba | ||
|
|
5a3565785c | ||
|
|
0f2a7215bb | ||
|
|
61e9371849 | ||
|
|
dde40b3a71 | ||
|
|
ebb2786748 | ||
|
|
28f256d2a6 | ||
|
|
883f5a3824 | ||
|
|
ac33d5dea3 | ||
|
|
604029568c | ||
|
|
eac5ac8426 | ||
|
|
7e9ad9e733 | ||
|
|
e2090772f3 | ||
|
|
525b88e9f1 | ||
|
|
3c90a84f68 | ||
|
|
ea33db388b | ||
|
|
f68d65ed59 | ||
|
|
3c7621049c | ||
|
|
dd60a8a4c9 | ||
|
|
5bd6a9c164 | ||
|
|
c743e5d9f3 | ||
|
|
5ca69458d4 | ||
|
|
bb9731b561 | ||
|
|
a2f4d1bbe7 | ||
|
|
88c13639bc | ||
|
|
28c1ba6c1c | ||
|
|
a2764283ba | ||
|
|
0ffd566957 | ||
|
|
5b54ac835d | ||
|
|
5f6a25fb58 | ||
|
|
d6d9286242 | ||
|
|
4c964ae655 | ||
|
|
8000d425ec | ||
|
|
c7cd84b1a4 | ||
|
|
6a89599fa5 | ||
|
|
5f40fa9bc6 | ||
|
|
24cde8e974 | ||
|
|
dea80b20e9 | ||
|
|
197d64d9a8 | ||
|
|
a2bbbfe2d5 | ||
|
|
2e9fac0b71 | ||
|
|
83e0e3bd8d | ||
|
|
2f651966e7 | ||
|
|
ffbaa4afea | ||
|
|
e11db6e8e4 | ||
|
|
eea6c7f41b | ||
|
|
01f53236a4 | ||
|
|
c39326616c | ||
|
|
b1aef26464 | ||
|
|
97117bfaf2 | ||
|
|
f12262881d | ||
|
|
95b0cdcb5e | ||
|
|
0f8e7b453e | ||
|
|
516b58b287 | ||
|
|
e53acb4150 | ||
|
|
7de475318a | ||
|
|
6dda514ec6 | ||
|
|
72a3a33e33 | ||
|
|
d26926a582 | ||
|
|
0731206b9d | ||
|
|
c2451ddd03 | ||
|
|
88adf84fc2 | ||
|
|
94c97765c8 | ||
|
|
1c56d15836 | ||
|
|
7985ef37d4 | ||
|
|
8f4f6d6ac3 | ||
|
|
4a065642f2 | ||
|
|
3276db0bdc | ||
|
|
88757f00e7 | ||
|
|
6d79ac9fde | ||
|
|
25af28946e | ||
|
|
950f5c186c | ||
|
|
8f96cbdabf | ||
|
|
17b3611c53 | ||
|
|
9240a20d13 | ||
|
|
6220b20659 | ||
|
|
2feb0acd7d | ||
|
|
8efd111426 | ||
|
|
616e9bf275 | ||
|
|
78f0f5855f | ||
|
|
0f11f65682 | ||
|
|
43363936cd | ||
|
|
0f25eb9c9a | ||
|
|
8ceb76b3f6 | ||
|
|
25e6db5e82 | ||
|
|
7c7864d500 | ||
|
|
a9761079e6 | ||
|
|
20fad922bc | ||
|
|
3cef022a15 | ||
|
|
52b0450953 | ||
|
|
7b725553ff | ||
|
|
e811651b00 | ||
|
|
fbba7714e4 | ||
|
|
bdb36ab626 | ||
|
|
1a466d9641 | ||
|
|
94f99aaeb3 | ||
|
|
851980e2a9 | ||
|
|
88c766afb0 | ||
|
|
13254a30df | ||
|
|
50af2650bb | ||
|
|
788562715e | ||
|
|
0888dd468f | ||
|
|
fd9a493868 | ||
|
|
661fdb2b26 | ||
|
|
23f267bb86 | ||
|
|
4af138f4fb | ||
|
|
10bc578bfe | ||
|
|
2406e58386 | ||
|
|
5cd212c51c | ||
|
|
fd6540a9e5 | ||
|
|
521e240c5f | ||
|
|
b4fa42a282 | ||
|
|
fb72e1b448 | ||
|
|
da9d312185 | ||
|
|
96c969687a | ||
|
|
f83844408f | ||
|
|
b247423184 | ||
|
|
9c77113e21 | ||
|
|
91857c2c0a | ||
|
|
886dadaaff | ||
|
|
d574162da3 | ||
|
|
0aa1ef6639 | ||
|
|
33c61b8708 | ||
|
|
bedf3bff0e | ||
|
|
d838eaecd2 | ||
|
|
cf3d83ee4f | ||
|
|
7247c32990 | ||
|
|
75ae73ee97 | ||
|
|
753440682e | ||
|
|
53f39c88e4 | ||
|
|
d3e3021a3d | ||
|
|
e46e1269a2 | ||
|
|
a5f7412296 | ||
|
|
b198434694 | ||
|
|
58998748e3 | ||
|
|
6bddca011c | ||
|
|
f147cb1133 | ||
|
|
d06efd0dd1 | ||
|
|
96132587b7 | ||
|
|
5d273f4630 | ||
|
|
95cec459a8 | ||
|
|
3b4fa2aa9c | ||
|
|
54e29eede1 | ||
|
|
c159b9debd | ||
|
|
eecdf31601 | ||
|
|
7f2f025866 | ||
|
|
ed3564831c | ||
|
|
8c1e518ab7 | ||
|
|
c7a862ec19 | ||
|
|
6e3bd38600 | ||
|
|
e6bdaea73e | ||
|
|
ebb9df07f3 | ||
|
|
8df917f1df | ||
|
|
30f72c48c4 | ||
|
|
e03924895b | ||
|
|
af055ec69c | ||
|
|
9d8ad4cc04 | ||
|
|
a6ef8efb72 | ||
|
|
ccc1262a3e | ||
|
|
656324f686 | ||
|
|
bd1ef659e8 | ||
|
|
afc61c2576 | ||
|
|
4d5373d626 | ||
|
|
609737322d | ||
|
|
fa63c92cf7 | ||
|
|
e5fabb4c5f | ||
|
|
bb91b96286 | ||
|
|
fd23946de3 | ||
|
|
a2b8c44e8f | ||
|
|
0a1de619b4 | ||
|
|
31d5c92dae | ||
|
|
d1873f8e1e | ||
|
|
58ba47841c | ||
|
|
0f2d6ac27a | ||
|
|
76ce422590 | ||
|
|
1f773d8e65 | ||
|
|
4451d2fec7 | ||
|
|
0ef8053919 | ||
|
|
140a45081f | ||
|
|
ebdaf0177d | ||
|
|
f2f11bc574 | ||
|
|
5f2a402b19 | ||
|
|
5f226bc82e | ||
|
|
a892887b04 | ||
|
|
d1ac00f64f | ||
|
|
03e1770a24 | ||
|
|
42adadd0cb | ||
|
|
3708ab3514 | ||
|
|
c9334d140b | ||
|
|
35e992c37c | ||
|
|
a92c28840b | ||
|
|
c96994dd8d | ||
|
|
bb6f0d5e91 | ||
|
|
bf9a3a4ca8 | ||
|
|
eef47e951e | ||
|
|
17892556f8 | ||
|
|
b65c1c26aa | ||
|
|
bc3e1ada03 | ||
|
|
1f82efa86d | ||
|
|
94c00229f2 | ||
|
|
5614ef2fed | ||
|
|
8ff5ca81d2 | ||
|
|
ff60c0af87 | ||
|
|
89398e5c87 | ||
|
|
f8d9f498d0 | ||
|
|
30738a092b | ||
|
|
f527138e6c | ||
|
|
24a4c3ceba | ||
|
|
48e25c1b9b | ||
|
|
f1a9e681ad | ||
|
|
f4cbf61604 | ||
|
|
5e990d9206 | ||
|
|
80db4dcf56 | ||
|
|
e557129121 | ||
|
|
c15e0ef9b8 | ||
|
|
e455709a31 | ||
|
|
c03ae0315e | ||
|
|
bc53dabce3 | ||
|
|
969a8f1fd9 | ||
|
|
0a7820f6de | ||
|
|
da935baa99 | ||
|
|
cc8483a07a | ||
|
|
48c4262f66 | ||
|
|
d069a4e482 | ||
|
|
2061919b64 | ||
|
|
a7110a4e08 | ||
|
|
b7be5a4fe2 | ||
|
|
876109267a | ||
|
|
1c0f9e1f30 | ||
|
|
6efafb348e | ||
|
|
36b4b797c1 | ||
|
|
ce1093406a | ||
|
|
dcffe8e60b | ||
|
|
0516637e8d | ||
|
|
3bc66c2f00 | ||
|
|
742c4370b5 | ||
|
|
292ad9d9ac | ||
|
|
3457bd77eb | ||
|
|
1874e9be81 | ||
|
|
3f86d803d2 | ||
|
|
26bf2ceb15 | ||
|
|
bfa15c61f1 | ||
|
|
61619c4db1 | ||
|
|
50be49544d | ||
|
|
b1d3c7ef52 | ||
|
|
7dff25f7c9 | ||
|
|
ab4fe548f2 | ||
|
|
c4cf0d78e1 | ||
|
|
d1f7400829 | ||
|
|
ee8cdb667d | ||
|
|
24114ba631 | ||
|
|
9d171609da | ||
|
|
518d2449a7 | ||
|
|
a906a76ccd | ||
|
|
af20905f7d | ||
|
|
d536a20019 | ||
|
|
bcefb04d54 | ||
|
|
dac2655915 | ||
|
|
14180ad185 | ||
|
|
dbf88f2750 | ||
|
|
0a3ff6bd70 | ||
|
|
d6f6c29a63 | ||
|
|
c7d1b8de9e | ||
|
|
b4628abc60 | ||
|
|
aef675c79b | ||
|
|
41602124c7 | ||
|
|
5c246649e2 | ||
|
|
6c9f187884 | ||
|
|
871575b06c | ||
|
|
fd4ba3f47e | ||
|
|
204b072388 | ||
|
|
e7ab318107 | ||
|
|
52d72157c0 | ||
|
|
ac47866c4c | ||
|
|
c47021150e | ||
|
|
a39b2e95c1 | ||
|
|
d42d3f45f0 | ||
|
|
b4494e588f | ||
|
|
0697a39145 | ||
|
|
e43b0cec4a | ||
|
|
ab4f282b03 | ||
|
|
4bc9075d0b | ||
|
|
faddf412f9 | ||
|
|
e22a44d79e | ||
|
|
4cc03d2239 | ||
|
|
1e929b0aa0 | ||
|
|
13b393a5e3 | ||
|
|
6166e7961e | ||
|
|
370e539a7e | ||
|
|
fd1f9f22e9 | ||
|
|
bcb7cc864c | ||
|
|
de2f753546 | ||
|
|
2cf06bcf48 | ||
|
|
cf954bf006 | ||
|
|
9d2083fa08 | ||
|
|
6a9745b42b | ||
|
|
87161df2f0 | ||
|
|
7ef07b576f | ||
|
|
6e63799a7d | ||
|
|
841fe75326 | ||
|
|
f6465c5202 | ||
|
|
14ac0512a9 | ||
|
|
f2722952e7 | ||
|
|
b7919bd3e6 | ||
|
|
7a55484b79 | ||
|
|
670d6e8639 | ||
|
|
6313934d94 | ||
|
|
40fced2450 | ||
|
|
692bf5cfb7 | ||
|
|
66e28eb52e | ||
|
|
3388a9659b | ||
|
|
eb88c483fb | ||
|
|
2c62166ff6 | ||
|
|
3101e43aa6 | ||
|
|
a49c757b8a | ||
|
|
3ad3dc08b8 | ||
|
|
eee919174d | ||
|
|
e802f5aabd | ||
|
|
e3c4b5b77e | ||
|
|
4c7179bc87 | ||
|
|
f4b1fc479d | ||
|
|
e1425de8a4 | ||
|
|
8ff2ea4b38 | ||
|
|
e7a77e438d | ||
|
|
a05628443e | ||
|
|
d09882f970 | ||
|
|
e5ec2a3adf | ||
|
|
d02af44331 | ||
|
|
251ff447bc | ||
|
|
4a1d9a9116 | ||
|
|
ceccbf3678 | ||
|
|
e930f47861 | ||
|
|
d630ed55f3 | ||
|
|
bec87f7ff8 | ||
|
|
045b8a35a2 | ||
|
|
8eb980db73 | ||
|
|
8440e1719f | ||
|
|
ddcd48edd8 | ||
|
|
9bc01da9c4 | ||
|
|
2a86f7d82f | ||
|
|
7287d67e7a | ||
|
|
c61ca0d27b | ||
|
|
a6dfc130c9 | ||
|
|
654475b7d6 | ||
|
|
fbeff00761 | ||
|
|
4b15c8459a | ||
|
|
893b695a61 | ||
|
|
659aaf6861 | ||
|
|
d68545d8de | ||
|
|
f5ae7148dd | ||
|
|
5329f95096 | ||
|
|
9a3f41e63b | ||
|
|
61bc15b1aa | ||
|
|
5bad06d4b6 |
No files matched your search
@@ -4,7 +4,6 @@ omit =
|
|||||||
jedi/inference/compiled/subprocess/__main__.py
|
jedi/inference/compiled/subprocess/__main__.py
|
||||||
jedi/__main__.py
|
jedi/__main__.py
|
||||||
# For now this is not being used.
|
# For now this is not being used.
|
||||||
jedi/refactoring.py
|
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_size = 2
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# all end-of-lines are normalized to LF when written to the repository
|
||||||
|
# https://git-scm.com/docs/gitattributes#_text
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# force all text files on the working dir to have LF line endings
|
||||||
|
# https://git-scm.com/docs/gitattributes#_eol
|
||||||
|
* text eol=lf
|
||||||
|
|
||||||
|
# PNGs are not text and should not be normalized
|
||||||
|
*.png -text
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: ci
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-20.04, windows-2019]
|
||||||
|
python-version: [3.9, 3.8, 3.7, 3.6]
|
||||||
|
environment: ['3.8', '3.9', '3.7', '3.6', 'interpreter']
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
if: ${{ matrix.environment != 'interpreter' }}
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.environment }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: 'pip install .[testing]'
|
||||||
|
|
||||||
|
- name: Setup tmate session
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: python -m pytest
|
||||||
|
env:
|
||||||
|
JEDI_TEST_ENVIRONMENT: ${{ matrix.environment }}
|
||||||
|
|
||||||
|
code-quality:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: 'pip install .[qa]'
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
python -m flake8 jedi setup.py
|
||||||
|
python -m mypy jedi sith.py
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: 'pip install .[testing] coverage'
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
python -m coverage run --source jedi -m pytest
|
||||||
|
python -m coverage report
|
||||||
|
|
||||||
|
- name: Upload coverage data
|
||||||
|
run: |
|
||||||
|
pip install --quiet codecov coveralls
|
||||||
|
python -m coverage xml
|
||||||
|
python -m coverage report -m
|
||||||
|
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
|
||||||
+2
-1
@@ -2,7 +2,6 @@
|
|||||||
*.sw?
|
*.sw?
|
||||||
*.pyc
|
*.pyc
|
||||||
.ropeproject
|
.ropeproject
|
||||||
.tox
|
|
||||||
.coveralls.yml
|
.coveralls.yml
|
||||||
.coverage
|
.coverage
|
||||||
.idea
|
.idea
|
||||||
@@ -13,3 +12,5 @@ jedi.egg-info/
|
|||||||
record.json
|
record.json
|
||||||
/.cache/
|
/.cache/
|
||||||
/.pytest_cache
|
/.pytest_cache
|
||||||
|
/.mypy_cache
|
||||||
|
/venv/
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
[submodule "jedi/third_party/typeshed"]
|
[submodule "jedi/third_party/typeshed"]
|
||||||
path = jedi/third_party/typeshed
|
path = jedi/third_party/typeshed
|
||||||
url = https://github.com/davidhalter/typeshed.git
|
url = https://github.com/davidhalter/typeshed.git
|
||||||
|
[submodule "jedi/third_party/django-stubs"]
|
||||||
|
path = jedi/third_party/django-stubs
|
||||||
|
url = https://github.com/davidhalter/django-stubs
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
python:
|
||||||
|
pip_install: true
|
||||||
-73
@@ -1,73 +0,0 @@
|
|||||||
dist: xenial
|
|
||||||
language: python
|
|
||||||
python:
|
|
||||||
- 2.7
|
|
||||||
- 3.4
|
|
||||||
- 3.5
|
|
||||||
- 3.6
|
|
||||||
- 3.7
|
|
||||||
- 3.8
|
|
||||||
|
|
||||||
env:
|
|
||||||
- JEDI_TEST_ENVIRONMENT=27
|
|
||||||
- JEDI_TEST_ENVIRONMENT=34
|
|
||||||
- JEDI_TEST_ENVIRONMENT=35
|
|
||||||
- JEDI_TEST_ENVIRONMENT=36
|
|
||||||
- JEDI_TEST_ENVIRONMENT=37
|
|
||||||
- JEDI_TEST_ENVIRONMENT=38
|
|
||||||
- JEDI_TEST_ENVIRONMENT=interpreter
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- python: 3.7
|
|
||||||
env:
|
|
||||||
- TOXENV=cov-py37
|
|
||||||
- JEDI_TEST_ENVIRONMENT=37
|
|
||||||
# For now ignore pypy, there are so many issues that we don't really need
|
|
||||||
# to run it.
|
|
||||||
#- python: pypy
|
|
||||||
# The 3.9 dev build does not seem to be available end 2019.
|
|
||||||
#- python: 3.9-dev
|
|
||||||
# env:
|
|
||||||
# - JEDI_TEST_ENVIRONMENT=39
|
|
||||||
install:
|
|
||||||
- pip install --quiet tox-travis
|
|
||||||
- sudo apt-get -y install python3-venv
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
# Setup/install Python for $JEDI_TEST_ENVIRONMENT.
|
|
||||||
set -ex
|
|
||||||
test_env_version=${JEDI_TEST_ENVIRONMENT:0:1}.${JEDI_TEST_ENVIRONMENT:1:1}
|
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" != "$test_env_version" ] && [ "$JEDI_TEST_ENVIRONMENT" != "interpreter" ]; then
|
|
||||||
python_bin=python$test_env_version
|
|
||||||
python_path="$(which $python_bin || true)"
|
|
||||||
if [ -z "$python_path" ]; then
|
|
||||||
# Only required for JEDI_TEST_ENVIRONMENT=34.
|
|
||||||
download_name=python-$test_env_version
|
|
||||||
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2
|
|
||||||
sudo tar xjf $download_name.tar.bz2 --directory / opt/python
|
|
||||||
ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin
|
|
||||||
elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then
|
|
||||||
# Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36).
|
|
||||||
pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)"
|
|
||||||
ln -s "$pyenv_bin" /home/travis/bin/$python_bin
|
|
||||||
fi
|
|
||||||
$python_bin --version
|
|
||||||
python_ver=$($python_bin -c 'import sys; print("%d%d" % sys.version_info[0:2])')
|
|
||||||
if [ "$JEDI_TEST_ENVIRONMENT" != "$python_ver" ]; then
|
|
||||||
echo "Unexpected Python version for $JEDI_TEST_ENVIRONMENT: $python_ver"
|
|
||||||
set +ex
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
set +ex
|
|
||||||
- tox
|
|
||||||
after_script:
|
|
||||||
- |
|
|
||||||
if [ $TOXENV == "cov-py37" ]; then
|
|
||||||
pip install --quiet codecov coveralls
|
|
||||||
coverage xml
|
|
||||||
coverage report -m
|
|
||||||
coveralls
|
|
||||||
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
|
|
||||||
fi
|
|
||||||
+60
-52
@@ -1,59 +1,67 @@
|
|||||||
Main Authors
|
Main Authors
|
||||||
============
|
------------
|
||||||
|
|
||||||
David Halter (@davidhalter) <davidhalter88@gmail.com>
|
- David Halter (@davidhalter) <davidhalter88@gmail.com>
|
||||||
Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
|
- Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
|
||||||
|
|
||||||
Code Contributors
|
Code Contributors
|
||||||
=================
|
-----------------
|
||||||
|
|
||||||
Danilo Bargen (@dbrgn) <mail@dbrgn.ch>
|
- Danilo Bargen (@dbrgn) <mail@dbrgn.ch>
|
||||||
Laurens Van Houtven (@lvh) <_@lvh.cc>
|
- Laurens Van Houtven (@lvh) <_@lvh.cc>
|
||||||
Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com>
|
- Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com>
|
||||||
Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch>
|
- Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch>
|
||||||
tek (@tek)
|
- tek (@tek)
|
||||||
Yasha Borevich (@jjay) <j.borevich@gmail.com>
|
- Yasha Borevich (@jjay) <j.borevich@gmail.com>
|
||||||
Aaron Griffin <aaronmgriffin@gmail.com>
|
- Aaron Griffin <aaronmgriffin@gmail.com>
|
||||||
andviro (@andviro)
|
- andviro (@andviro)
|
||||||
Mike Gilbert (@floppym) <floppym@gentoo.org>
|
- Mike Gilbert (@floppym) <floppym@gentoo.org>
|
||||||
Aaron Meurer (@asmeurer) <asmeurer@gmail.com>
|
- Aaron Meurer (@asmeurer) <asmeurer@gmail.com>
|
||||||
Lubos Trilety <ltrilety@redhat.com>
|
- Lubos Trilety <ltrilety@redhat.com>
|
||||||
Akinori Hattori (@hattya) <hattya@gmail.com>
|
- Akinori Hattori (@hattya) <hattya@gmail.com>
|
||||||
srusskih (@srusskih)
|
- srusskih (@srusskih)
|
||||||
Steven Silvester (@blink1073)
|
- Steven Silvester (@blink1073)
|
||||||
Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com>
|
- Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com>
|
||||||
Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de>
|
- Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de>
|
||||||
Fredrik Bergroth (@fbergroth)
|
- Fredrik Bergroth (@fbergroth)
|
||||||
Mathias Fußenegger (@mfussenegger)
|
- Mathias Fußenegger (@mfussenegger)
|
||||||
Syohei Yoshida (@syohex) <syohex@gmail.com>
|
- Syohei Yoshida (@syohex) <syohex@gmail.com>
|
||||||
ppalucky (@ppalucky)
|
- ppalucky (@ppalucky)
|
||||||
immerrr (@immerrr) immerrr@gmail.com
|
- immerrr (@immerrr) immerrr@gmail.com
|
||||||
Albertas Agejevas (@alga)
|
- Albertas Agejevas (@alga)
|
||||||
Savor d'Isavano (@KenetJervet) <newelevenken@163.com>
|
- Savor d'Isavano (@KenetJervet) <newelevenken@163.com>
|
||||||
Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com>
|
- Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com>
|
||||||
Ian Lee (@IanLee1521) <IanLee1521@gmail.com>
|
- Ian Lee (@IanLee1521) <IanLee1521@gmail.com>
|
||||||
Farkhad Khatamov (@hatamov) <comsgn@gmail.com>
|
- Farkhad Khatamov (@hatamov) <comsgn@gmail.com>
|
||||||
Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net>
|
- Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net>
|
||||||
Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
|
- Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
|
||||||
Reinoud Elhorst (@reinhrst)
|
- Reinoud Elhorst (@reinhrst)
|
||||||
Guido van Rossum (@gvanrossum) <guido@python.org>
|
- Guido van Rossum (@gvanrossum) <guido@python.org>
|
||||||
Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
- Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
||||||
Cristi Burcă (@scribu)
|
- Cristi Burcă (@scribu)
|
||||||
bstaint (@bstaint)
|
- bstaint (@bstaint)
|
||||||
Mathias Rav (@Mortal) <rav@cs.au.dk>
|
- Mathias Rav (@Mortal) <rav@cs.au.dk>
|
||||||
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
- Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
||||||
Simon Ruggier (@sruggier)
|
- Simon Ruggier (@sruggier)
|
||||||
Élie Gouzien (@ElieGouzien)
|
- Élie Gouzien (@ElieGouzien)
|
||||||
Robin Roth (@robinro)
|
- Robin Roth (@robinro)
|
||||||
Malte Plath (@langsamer)
|
- Malte Plath (@langsamer)
|
||||||
Anton Zub (@zabulazza)
|
- Anton Zub (@zabulazza)
|
||||||
Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
|
- Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
|
||||||
Tobias Rzepka (@TobiasRzepka)
|
- Tobias Rzepka (@TobiasRzepka)
|
||||||
micbou (@micbou)
|
- micbou (@micbou)
|
||||||
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
- Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
||||||
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
- Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
||||||
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
|
- Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
|
||||||
Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com>
|
- Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com>
|
||||||
Tim Gates (@timgates42) <tim.gates@iress.com>
|
- Tim Gates (@timgates42) <tim.gates@iress.com>
|
||||||
|
- Lior Goldberg (@goldberglior)
|
||||||
|
- Ryan Clary (@mrclary)
|
||||||
|
- Max Mäusezahl (@mmaeusezahl) <maxmaeusezahl@googlemail.com>
|
||||||
|
- Vladislav Serebrennikov (@endilll)
|
||||||
|
- Andrii Kolomoiets (@muffinmad)
|
||||||
|
- Leo Ryu (@Leo-Ryu)
|
||||||
|
|
||||||
|
And a few more "anonymous" contributors.
|
||||||
|
|
||||||
Note: (@user) means a github user name.
|
Note: (@user) means a github user name.
|
||||||
+80
-8
@@ -3,6 +3,78 @@
|
|||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
Unreleased
|
||||||
|
++++++++++
|
||||||
|
|
||||||
|
- Implict namespaces are now a separate types in ``Name().type``
|
||||||
|
|
||||||
|
0.18.0 (2020-12-25)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Dropped Python 2 and Python 3.5
|
||||||
|
- Using ``pathlib.Path()`` as an output instead of ``str`` in most places:
|
||||||
|
- ``Project.path``
|
||||||
|
- ``Script.path``
|
||||||
|
- ``Definition.module_path``
|
||||||
|
- ``Refactoring.get_renames``
|
||||||
|
- ``Refactoring.get_changed_files``
|
||||||
|
- Functions with ``@property`` now return ``property`` instead of ``function``
|
||||||
|
in ``Name().type``
|
||||||
|
- Started using annotations
|
||||||
|
- Better support for the walrus operator
|
||||||
|
- Project attributes are now read accessible
|
||||||
|
- Removed all deprecations
|
||||||
|
|
||||||
|
This is likely going to be the last minor release before 1.0.
|
||||||
|
|
||||||
|
0.17.2 (2020-07-17)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Added an option to pass environment variables to ``Environment``
|
||||||
|
- ``Project(...).path`` exists now
|
||||||
|
- Support for Python 3.9
|
||||||
|
- A few bugfixes
|
||||||
|
|
||||||
|
This will be the last release that supports Python 2 and Python 3.5.
|
||||||
|
``0.18.0`` will be Python 3.6+.
|
||||||
|
|
||||||
|
0.17.1 (2020-06-20)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Django ``Model`` meta class support
|
||||||
|
- Django Manager support (completion on Managers/QuerySets)
|
||||||
|
- Added Django Stubs to Jedi, thanks to all contributors of the
|
||||||
|
`Django Stubs <https://github.com/typeddjango/django-stubs>`_ project
|
||||||
|
- Added ``SyntaxError.get_message``
|
||||||
|
- Python 3.9 support
|
||||||
|
- Bugfixes (mostly towards Generics)
|
||||||
|
|
||||||
|
0.17.0 (2020-04-14)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Added ``Project`` support. This allows a user to specify which folders Jedi
|
||||||
|
should work with.
|
||||||
|
- Added support for Refactoring. The following refactorings have been
|
||||||
|
implemented: ``Script.rename``, ``Script.inline``,
|
||||||
|
``Script.extract_variable`` and ``Script.extract_function``.
|
||||||
|
- Added ``Script.get_syntax_errors`` to display syntax errors in the current
|
||||||
|
script.
|
||||||
|
- Added code search capabilities both for individual files and projects. The
|
||||||
|
new functions are ``Project.search``, ``Project.complete_search``,
|
||||||
|
``Script.search`` and ``Script.complete_search``.
|
||||||
|
- Added ``Script.help`` to make it easier to display a help window to people.
|
||||||
|
Now returns pydoc information as well for Python keywords/operators. This
|
||||||
|
means that on the class keyword it will now return the docstring of Python's
|
||||||
|
builtin function ``help('class')``.
|
||||||
|
- The API documentation is now way more readable and complete. Check it out
|
||||||
|
under https://jedi.readthedocs.io. A lot of it has been rewritten.
|
||||||
|
- Removed Python 3.4 support
|
||||||
|
- Many bugfixes
|
||||||
|
|
||||||
|
This is likely going to be the last minor version that supports Python 2 and
|
||||||
|
Python3.5. Bugfixes will be provided in 0.17.1+. The next minor/major version
|
||||||
|
will probably be Jedi 1.0.0.
|
||||||
|
|
||||||
0.16.0 (2020-01-26)
|
0.16.0 (2020-01-26)
|
||||||
+++++++++++++++++++
|
+++++++++++++++++++
|
||||||
|
|
||||||
@@ -25,8 +97,8 @@ Changelog
|
|||||||
- ``call_signatures`` deprecated, use ``get_signatures`` instead
|
- ``call_signatures`` deprecated, use ``get_signatures`` instead
|
||||||
- ``usages`` deprecated, use ``get_references`` instead
|
- ``usages`` deprecated, use ``get_references`` instead
|
||||||
- ``jedi.names`` deprecated, use ``jedi.Script(...).get_names()``
|
- ``jedi.names`` deprecated, use ``jedi.Script(...).get_names()``
|
||||||
- ``BaseDefinition.goto_assignments`` renamed to ``BaseDefinition.goto``
|
- ``BaseName.goto_assignments`` renamed to ``BaseName.goto``
|
||||||
- Add follow_imports to ``Definition.goto``. Now its signature matches
|
- Add follow_imports to ``Name.goto``. Now its signature matches
|
||||||
``Script.goto``.
|
``Script.goto``.
|
||||||
- **Python 2 support deprecated**. For this release it is best effort. Python 2
|
- **Python 2 support deprecated**. For this release it is best effort. Python 2
|
||||||
has reached the end of its life and now it's just about a smooth transition.
|
has reached the end of its life and now it's just about a smooth transition.
|
||||||
@@ -66,13 +138,13 @@ Changelog
|
|||||||
|
|
||||||
New APIs:
|
New APIs:
|
||||||
|
|
||||||
- ``Definition.get_signatures() -> List[Signature]``. Signatures are similar to
|
- ``Name.get_signatures() -> List[Signature]``. Signatures are similar to
|
||||||
``CallSignature``. ``Definition.params`` is therefore deprecated.
|
``CallSignature``. ``Name.params`` is therefore deprecated.
|
||||||
- ``Signature.to_string()`` to format signatures.
|
- ``Signature.to_string()`` to format signatures.
|
||||||
- ``Signature.params -> List[ParamDefinition]``, ParamDefinition has the
|
- ``Signature.params -> List[ParamName]``, ParamName has the
|
||||||
following additional attributes ``infer_default()``, ``infer_annotation()``,
|
following additional attributes ``infer_default()``, ``infer_annotation()``,
|
||||||
``to_string()``, and ``kind``.
|
``to_string()``, and ``kind``.
|
||||||
- ``Definition.execute() -> List[Definition]``, makes it possible to infer
|
- ``Name.execute() -> List[Name]``, makes it possible to infer
|
||||||
return values of functions.
|
return values of functions.
|
||||||
|
|
||||||
|
|
||||||
@@ -88,7 +160,7 @@ New APIs:
|
|||||||
- Added ``goto_*(prefer_stubs=True)`` as well as ``goto_*(prefer_stubs=True)``
|
- Added ``goto_*(prefer_stubs=True)`` as well as ``goto_*(prefer_stubs=True)``
|
||||||
- Stubs are used now for type inference
|
- Stubs are used now for type inference
|
||||||
- Typeshed is used for better type inference
|
- Typeshed is used for better type inference
|
||||||
- Reworked Definition.full_name, should have more correct return values
|
- Reworked Name.full_name, should have more correct return values
|
||||||
|
|
||||||
0.13.3 (2019-02-24)
|
0.13.3 (2019-02-24)
|
||||||
+++++++++++++++++++
|
+++++++++++++++++++
|
||||||
@@ -168,7 +240,7 @@ New APIs:
|
|||||||
- Actual semantic completions for the complete Python syntax.
|
- Actual semantic completions for the complete Python syntax.
|
||||||
- Basic type inference for ``yield from`` PEP 380.
|
- Basic type inference for ``yield from`` PEP 380.
|
||||||
- PEP 484 support (most of the important features of it). Thanks Claude! (@reinhrst)
|
- PEP 484 support (most of the important features of it). Thanks Claude! (@reinhrst)
|
||||||
- Added ``get_line_code`` to ``Definition`` and ``Completion`` objects.
|
- Added ``get_line_code`` to ``Name`` and ``Completion`` objects.
|
||||||
- Completely rewritten the type inference engine.
|
- Completely rewritten the type inference engine.
|
||||||
- A new and better parser for (fast) parsing diffs of Python code.
|
- A new and better parser for (fast) parsing diffs of Python code.
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -6,11 +6,9 @@ include .coveragerc
|
|||||||
include sith.py
|
include sith.py
|
||||||
include conftest.py
|
include conftest.py
|
||||||
include pytest.ini
|
include pytest.ini
|
||||||
include tox.ini
|
|
||||||
include requirements.txt
|
|
||||||
include jedi/parser/python/grammar*.txt
|
|
||||||
recursive-include jedi/third_party *.pyi
|
recursive-include jedi/third_party *.pyi
|
||||||
include jedi/third_party/typeshed/LICENSE
|
include jedi/third_party/typeshed/LICENSE
|
||||||
|
include jedi/third_party/django-stubs/LICENSE.txt
|
||||||
include jedi/third_party/typeshed/README
|
include jedi/third_party/typeshed/README
|
||||||
recursive-include test *
|
recursive-include test *
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
|
|||||||
+94
-103
@@ -1,117 +1,106 @@
|
|||||||
###################################################################
|
####################################################################################
|
||||||
Jedi - an awesome autocompletion/static analysis library for Python
|
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
|
||||||
###################################################################
|
####################################################################################
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/jedi.svg?style=flat
|
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
|
||||||
:target: https://pypi.python.org/pypi/jedi
|
:target: https://github.com/davidhalter/jedi/issues
|
||||||
:alt: PyPI version
|
:alt: The percentage of open issues and pull requests
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/jedi.svg
|
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
|
||||||
:target: https://pypi.python.org/pypi/jedi
|
:target: https://github.com/davidhalter/jedi/issues
|
||||||
:alt: Supported Python versions
|
:alt: The resolution time is the median time an issue or pull request stays open.
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master
|
.. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
|
||||||
:target: https://travis-ci.org/davidhalter/jedi
|
:target: https://github.com/davidhalter/jedi/actions
|
||||||
:alt: Linux Tests
|
:alt: Tests
|
||||||
|
|
||||||
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true
|
.. image:: https://pepy.tech/badge/jedi
|
||||||
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master
|
:target: https://pepy.tech/project/jedi
|
||||||
:alt: Windows Tests
|
:alt: PyPI Downloads
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
|
|
||||||
:target: https://coveralls.io/r/davidhalter/jedi
|
|
||||||
:alt: Coverage status
|
|
||||||
|
|
||||||
|
|
||||||
*If you have specific questions, please add an issue or ask on* `Stack Overflow
|
Jedi is a static analysis tool for Python that is typically used in
|
||||||
<https://stackoverflow.com/questions/tagged/python-jedi>`_ *with the label* ``python-jedi``.
|
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
|
||||||
|
functionality. Other features include refactoring, code search and finding
|
||||||
|
references.
|
||||||
|
|
||||||
|
Jedi has a simple API to work with. There is a reference implementation as a
|
||||||
Jedi is a static analysis tool for Python that can be used in IDEs/editors.
|
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
|
||||||
Jedi has a focus on autocompletion and goto functionality. Jedi is fast and is
|
REPL is also possible, IPython uses it natively and for the CPython REPL you
|
||||||
very well tested. It understands Python and stubs on a deep level.
|
can install it. Jedi is well tested and bugs should be rare.
|
||||||
|
|
||||||
Jedi has support for different goto functions. It's possible to search for
|
|
||||||
references and list names in a Python file to get information about them.
|
|
||||||
|
|
||||||
Jedi uses a very simple API to connect with IDE's. There's a reference
|
|
||||||
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
|
|
||||||
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
|
|
||||||
Autocompletion in your REPL is also possible, IPython uses it natively and for
|
|
||||||
the CPython REPL you have to install it.
|
|
||||||
|
|
||||||
Jedi can currently be used with the following editors/projects:
|
Jedi can currently be used with the following editors/projects:
|
||||||
|
|
||||||
- Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_)
|
- Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_)
|
||||||
|
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
|
||||||
- Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_)
|
- Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_)
|
||||||
- Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3])
|
- Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3])
|
||||||
- TextMate_ (Not sure if it's actually working)
|
- TextMate_ (Not sure if it's actually working)
|
||||||
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`proof
|
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`see
|
||||||
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
|
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
|
||||||
- Atom_ (autocomplete-python-jedi_)
|
- Atom_ (autocomplete-python-jedi_)
|
||||||
- `GNOME Builder`_ (with support for GObject Introspection)
|
- `GNOME Builder`_ (with support for GObject Introspection)
|
||||||
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
|
|
||||||
- Gedit (gedi_)
|
- Gedit (gedi_)
|
||||||
- wdb_ - Web Debugger
|
- wdb_ - Web Debugger
|
||||||
- `Eric IDE`_ (Available as a plugin)
|
- `Eric IDE`_ (Available as a plugin)
|
||||||
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
|
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
|
||||||
|
- `xonsh shell <https://xon.sh/contents.html>`_ has `jedi extension <https://xon.sh/xontribs.html#jedi>`_
|
||||||
|
|
||||||
and many more!
|
and many more!
|
||||||
|
|
||||||
|
There are a few language servers that use Jedi:
|
||||||
|
|
||||||
|
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
|
||||||
|
- `python-language-server <https://github.com/palantir/python-language-server>`_
|
||||||
|
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
|
||||||
|
|
||||||
Here are some pictures taken from jedi-vim_:
|
Here are some pictures taken from jedi-vim_:
|
||||||
|
|
||||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png
|
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png
|
||||||
|
|
||||||
Completion for almost anything (Ctrl+Space).
|
Completion for almost anything:
|
||||||
|
|
||||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_function.png
|
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_function.png
|
||||||
|
|
||||||
Display of function/class bodies, docstrings.
|
Documentation:
|
||||||
|
|
||||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_pydoc.png
|
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_pydoc.png
|
||||||
|
|
||||||
Pydoc support (Shift+k).
|
|
||||||
|
|
||||||
There is also support for goto and renaming.
|
|
||||||
|
|
||||||
Get the latest version from `github <https://github.com/davidhalter/jedi>`_
|
Get the latest version from `github <https://github.com/davidhalter/jedi>`_
|
||||||
(master branch should always be kind of stable/working).
|
(master branch should always be kind of stable/working).
|
||||||
|
|
||||||
Docs are available at `https://jedi.readthedocs.org/en/latest/
|
Docs are available at `https://jedi.readthedocs.org/en/latest/
|
||||||
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with documentation
|
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with enhancements
|
||||||
enhancements and/or fixes are awesome and most welcome. Jedi uses `semantic
|
and/or fixes are awesome and most welcome. Jedi uses `semantic versioning
|
||||||
versioning <https://semver.org/>`_.
|
<https://semver.org/>`_.
|
||||||
|
|
||||||
If you want to stay up-to-date (News / RFCs), please subscribe to this `github
|
If you want to stay **up-to-date** with releases, please **subscribe** to this
|
||||||
thread <https://github.com/davidhalter/jedi/issues/1063>`_.:
|
mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
|
||||||
|
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
|
||||||
|
|
||||||
|
Issues & Questions
|
||||||
|
==================
|
||||||
|
|
||||||
|
You can file issues and questions in the `issue tracker
|
||||||
|
<https://github.com/davidhalter/jedi/>`. Alternatively you can also ask on
|
||||||
|
`Stack Overflow <https://stackoverflow.com/questions/tagged/python-jedi>`_ with
|
||||||
|
the label ``python-jedi``.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
pip install jedi
|
`Check out the docs <https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
|
||||||
|
|
||||||
Note: This just installs the Jedi library, not the editor plugins. For
|
Features and Limitations
|
||||||
information about how to make it work with your editor, refer to the
|
========================
|
||||||
corresponding documentation.
|
|
||||||
|
|
||||||
You don't want to use ``pip``? Please refer to the `manual
|
Jedi's features are listed here:
|
||||||
<https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
|
`Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_.
|
||||||
|
|
||||||
|
You can run Jedi on Python 3.6+ but it should also
|
||||||
Feature Support and Caveats
|
understand code that is older than those versions. Additionally you should be
|
||||||
===========================
|
able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
|
||||||
|
|
||||||
Jedi really understands your Python code. For a comprehensive list what Jedi
|
|
||||||
understands, see: `Features
|
|
||||||
<https://jedi.readthedocs.org/en/latest/docs/features.html>`_. A list of
|
|
||||||
caveats can be found on the same page.
|
|
||||||
|
|
||||||
You can run Jedi on CPython 2.7 or 3.4+ but it should also
|
|
||||||
understand/parse code older than those versions. Additionally you should be able
|
|
||||||
to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
|
|
||||||
very well.
|
very well.
|
||||||
|
|
||||||
Tips on how to use Jedi efficiently can be found `here
|
Tips on how to use Jedi efficiently can be found `here
|
||||||
@@ -120,47 +109,62 @@ Tips on how to use Jedi efficiently can be found `here
|
|||||||
API
|
API
|
||||||
---
|
---
|
||||||
|
|
||||||
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
|
You can find a comprehensive documentation for the
|
||||||
|
`API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
|
||||||
|
|
||||||
|
Autocompletion / Goto / Documentation
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
Autocompletion / Goto / Pydoc
|
There are the following commands:
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Please check the API for a good explanation. There are the following commands:
|
|
||||||
|
|
||||||
- ``jedi.Script.goto``
|
- ``jedi.Script.goto``
|
||||||
|
- ``jedi.Script.infer``
|
||||||
|
- ``jedi.Script.help``
|
||||||
- ``jedi.Script.complete``
|
- ``jedi.Script.complete``
|
||||||
- ``jedi.Script.get_references``
|
- ``jedi.Script.get_references``
|
||||||
|
- ``jedi.Script.get_signatures``
|
||||||
|
- ``jedi.Script.get_context``
|
||||||
|
|
||||||
The returned objects are very powerful and really all you might need.
|
The returned objects are very powerful and are really all you might need.
|
||||||
|
|
||||||
|
|
||||||
Autocompletion in your REPL (IPython, etc.)
|
Autocompletion in your REPL (IPython, etc.)
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
Starting with IPython `6.0.0` Jedi is a dependency of IPython. Autocompletion
|
Jedi is a dependency of IPython. Autocompletion in IPython with Jedi is
|
||||||
in IPython is therefore possible without additional configuration.
|
therefore possible without additional configuration.
|
||||||
|
|
||||||
It's possible to have Jedi autocompletion in REPL modes - `example video <https://vimeo.com/122332037>`_.
|
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
|
||||||
This means that in Python you can enable tab completion in a `REPL
|
can look like.
|
||||||
|
For the ``python`` shell you can enable tab completion in a `REPL
|
||||||
<https://jedi.readthedocs.org/en/latest/docs/usage.html#tab-completion-in-the-python-shell>`_.
|
<https://jedi.readthedocs.org/en/latest/docs/usage.html#tab-completion-in-the-python-shell>`_.
|
||||||
|
|
||||||
|
|
||||||
Static Analysis
|
Static Analysis
|
||||||
------------------------
|
---------------
|
||||||
|
|
||||||
To do all forms of static analysis, please try to use
|
For a lot of forms of static analysis, you can try to use
|
||||||
``jedi.Script(...).get_names``. It will return a list of names that you can use
|
``jedi.Script(...).get_names``. It will return a list of names that you can
|
||||||
to infer types and so on.
|
then filter and work with. There is also a way to list the syntax errors in a
|
||||||
|
file: ``jedi.Script.get_syntax_errors``.
|
||||||
|
|
||||||
|
|
||||||
Refactoring
|
Refactoring
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Jedi's parser would support refactoring, but there's no API to use it right
|
Jedi supports the following refactorings:
|
||||||
now. If you're interested in helping out here, let me know. With the latest
|
|
||||||
parser changes, it should be very easy to actually make it work.
|
|
||||||
|
|
||||||
|
- ``jedi.Script.inline``
|
||||||
|
- ``jedi.Script.rename``
|
||||||
|
- ``jedi.Script.extract_function``
|
||||||
|
- ``jedi.Script.extract_variable``
|
||||||
|
|
||||||
|
Code Search
|
||||||
|
-----------
|
||||||
|
|
||||||
|
There is support for module search with ``jedi.Script.search``, and project
|
||||||
|
search for ``jedi.Project.search``. The way to search is either by providing a
|
||||||
|
name like ``foo`` or by using dotted syntax like ``foo.bar``. Additionally you
|
||||||
|
can provide the API type like ``class foo.bar.Bar``. There are also the
|
||||||
|
functions ``jedi.Script.complete_search`` and ``jedi.Project.complete_search``.
|
||||||
|
|
||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
@@ -168,39 +172,26 @@ Development
|
|||||||
There's a pretty good and extensive `development documentation
|
There's a pretty good and extensive `development documentation
|
||||||
<https://jedi.readthedocs.org/en/latest/docs/development.html>`_.
|
<https://jedi.readthedocs.org/en/latest/docs/development.html>`_.
|
||||||
|
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
The test suite depends on ``tox`` and ``pytest``::
|
The test suite uses ``pytest``::
|
||||||
|
|
||||||
pip install tox pytest
|
pip install pytest
|
||||||
|
|
||||||
To run the tests for all supported Python versions::
|
If you want to test only a specific Python version (e.g. Python 3.8), it is as
|
||||||
|
easy as::
|
||||||
|
|
||||||
tox
|
python3.8 -m pytest
|
||||||
|
|
||||||
If you want to test only a specific Python version (e.g. Python 2.7), it's as
|
|
||||||
easy as ::
|
|
||||||
|
|
||||||
tox -e py27
|
|
||||||
|
|
||||||
Tests are also run automatically on `Travis CI
|
|
||||||
<https://travis-ci.org/davidhalter/jedi/>`_.
|
|
||||||
|
|
||||||
For more detailed information visit the `testing documentation
|
For more detailed information visit the `testing documentation
|
||||||
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_.
|
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_.
|
||||||
|
|
||||||
|
|
||||||
Acknowledgements
|
Acknowledgements
|
||||||
================
|
================
|
||||||
|
|
||||||
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of
|
Thanks a lot to all the
|
||||||
other things.
|
`contributors <https://jedi.readthedocs.org/en/latest/docs/acknowledgements.html>`_!
|
||||||
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
|
|
||||||
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
|
|
||||||
(originally used in lib2to3).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- TOXENV: py27
|
|
||||||
PYTHON_PATH: C:\Python27
|
|
||||||
JEDI_TEST_ENVIRONMENT: 27
|
|
||||||
- TOXENV: py27
|
|
||||||
PYTHON_PATH: C:\Python27
|
|
||||||
JEDI_TEST_ENVIRONMENT: 34
|
|
||||||
- TOXENV: py27
|
|
||||||
PYTHON_PATH: C:\Python27
|
|
||||||
JEDI_TEST_ENVIRONMENT: 35
|
|
||||||
- TOXENV: py27
|
|
||||||
PYTHON_PATH: C:\Python27
|
|
||||||
JEDI_TEST_ENVIRONMENT: 36
|
|
||||||
- TOXENV: py27
|
|
||||||
PYTHON_PATH: C:\Python27
|
|
||||||
JEDI_TEST_ENVIRONMENT: 37
|
|
||||||
|
|
||||||
- TOXENV: py35
|
|
||||||
PYTHON_PATH: C:\Python35
|
|
||||||
JEDI_TEST_ENVIRONMENT: 27
|
|
||||||
- TOXENV: py35
|
|
||||||
PYTHON_PATH: C:\Python35
|
|
||||||
JEDI_TEST_ENVIRONMENT: 34
|
|
||||||
- TOXENV: py35
|
|
||||||
PYTHON_PATH: C:\Python35
|
|
||||||
JEDI_TEST_ENVIRONMENT: 35
|
|
||||||
- TOXENV: py35
|
|
||||||
PYTHON_PATH: C:\Python35
|
|
||||||
JEDI_TEST_ENVIRONMENT: 36
|
|
||||||
- TOXENV: py35
|
|
||||||
PYTHON_PATH: C:\Python35
|
|
||||||
JEDI_TEST_ENVIRONMENT: 37
|
|
||||||
|
|
||||||
- TOXENV: py36
|
|
||||||
PYTHON_PATH: C:\Python36
|
|
||||||
JEDI_TEST_ENVIRONMENT: 27
|
|
||||||
- TOXENV: py36
|
|
||||||
PYTHON_PATH: C:\Python36
|
|
||||||
JEDI_TEST_ENVIRONMENT: 34
|
|
||||||
- TOXENV: py36
|
|
||||||
PYTHON_PATH: C:\Python36
|
|
||||||
JEDI_TEST_ENVIRONMENT: 35
|
|
||||||
- TOXENV: py36
|
|
||||||
PYTHON_PATH: C:\Python36
|
|
||||||
JEDI_TEST_ENVIRONMENT: 36
|
|
||||||
- TOXENV: py36
|
|
||||||
PYTHON_PATH: C:\Python36
|
|
||||||
JEDI_TEST_ENVIRONMENT: 37
|
|
||||||
|
|
||||||
- TOXENV: py37
|
|
||||||
PYTHON_PATH: C:\Python37
|
|
||||||
JEDI_TEST_ENVIRONMENT: 27
|
|
||||||
- TOXENV: py37
|
|
||||||
PYTHON_PATH: C:\Python37
|
|
||||||
JEDI_TEST_ENVIRONMENT: 34
|
|
||||||
- TOXENV: py37
|
|
||||||
PYTHON_PATH: C:\Python37
|
|
||||||
JEDI_TEST_ENVIRONMENT: 35
|
|
||||||
- TOXENV: py37
|
|
||||||
PYTHON_PATH: C:\Python37
|
|
||||||
JEDI_TEST_ENVIRONMENT: 36
|
|
||||||
- TOXENV: py37
|
|
||||||
PYTHON_PATH: C:\Python37
|
|
||||||
JEDI_TEST_ENVIRONMENT: 37
|
|
||||||
install:
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
- set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH%
|
|
||||||
- pip install tox
|
|
||||||
build_script:
|
|
||||||
- tox
|
|
||||||
+26
-39
@@ -8,18 +8,16 @@ import pytest
|
|||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi.api.environment import get_system_environment, InterpreterEnvironment
|
from jedi.api.environment import get_system_environment, InterpreterEnvironment
|
||||||
from jedi._compatibility import py_version
|
from test.helpers import test_dir
|
||||||
|
|
||||||
collect_ignore = [
|
collect_ignore = [
|
||||||
'setup.py',
|
'setup.py',
|
||||||
'__main__.py',
|
'jedi/__main__.py',
|
||||||
'jedi/inference/compiled/subprocess/__main__.py',
|
'jedi/inference/compiled/subprocess/__main__.py',
|
||||||
'build/',
|
'build/',
|
||||||
'test/examples',
|
'test/examples',
|
||||||
|
'sith.py',
|
||||||
]
|
]
|
||||||
if sys.version_info < (3, 6):
|
|
||||||
# Python 2 not supported syntax
|
|
||||||
collect_ignore.append('test/test_inference/test_mixed.py')
|
|
||||||
|
|
||||||
|
|
||||||
# The following hooks (pytest_configure, pytest_unconfigure) are used
|
# The following hooks (pytest_configure, pytest_unconfigure) are used
|
||||||
@@ -44,7 +42,7 @@ def pytest_addoption(parser):
|
|||||||
help="Warnings are treated as errors.")
|
help="Warnings are treated as errors.")
|
||||||
|
|
||||||
parser.addoption("--env", action='store',
|
parser.addoption("--env", action='store',
|
||||||
help="Execute the tests in that environment (e.g. 35 for python3.5).")
|
help="Execute the tests in that environment (e.g. 39 for python3.9).")
|
||||||
parser.addoption("--interpreter-env", "-I", action='store_true',
|
parser.addoption("--interpreter-env", "-I", action='store_true',
|
||||||
help="Don't use subprocesses to guarantee having safe "
|
help="Don't use subprocesses to guarantee having safe "
|
||||||
"code execution. Useful for debugging.")
|
"code execution. Useful for debugging.")
|
||||||
@@ -96,12 +94,15 @@ def clean_jedi_cache(request):
|
|||||||
def environment(request):
|
def environment(request):
|
||||||
version = request.config.option.env
|
version = request.config.option.env
|
||||||
if version is None:
|
if version is None:
|
||||||
version = os.environ.get('JEDI_TEST_ENVIRONMENT', str(py_version))
|
v = str(sys.version_info[0]) + str(sys.version_info[1])
|
||||||
|
version = os.environ.get('JEDI_TEST_ENVIRONMENT', v)
|
||||||
|
|
||||||
if request.config.option.interpreter_env or version == 'interpreter':
|
if request.config.option.interpreter_env or version == 'interpreter':
|
||||||
return InterpreterEnvironment()
|
return InterpreterEnvironment()
|
||||||
|
|
||||||
return get_system_environment(version[0] + '.' + version[1:])
|
if '.' not in version:
|
||||||
|
version = version[0] + '.' + version[1:]
|
||||||
|
return get_system_environment(version)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
@@ -109,6 +110,12 @@ def Script(environment):
|
|||||||
return partial(jedi.Script, environment=environment)
|
return partial(jedi.Script, environment=environment)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def ScriptWithProject(Script):
|
||||||
|
project = jedi.Project(test_dir)
|
||||||
|
return partial(jedi.Script, project=project)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def get_names(Script):
|
def get_names(Script):
|
||||||
return lambda code, **kwargs: Script(code).get_names(**kwargs)
|
return lambda code, **kwargs: Script(code).get_names(**kwargs)
|
||||||
@@ -126,17 +133,21 @@ def goto_or_help(request, Script):
|
|||||||
|
|
||||||
@pytest.fixture(scope='session', params=['goto', 'help', 'infer'])
|
@pytest.fixture(scope='session', params=['goto', 'help', 'infer'])
|
||||||
def goto_or_help_or_infer(request, Script):
|
def goto_or_help_or_infer(request, Script):
|
||||||
|
def do(code, *args, **kwargs):
|
||||||
|
return getattr(Script(code), request.param)(*args, **kwargs)
|
||||||
|
|
||||||
|
do.type = request.param
|
||||||
|
return do
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session', params=['goto', 'complete', 'help'])
|
||||||
|
def goto_or_complete(request, Script):
|
||||||
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs)
|
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def has_typing(environment):
|
def has_django(environment):
|
||||||
if environment.version_info >= (3, 5, 0):
|
script = jedi.Script('import django', environment=environment)
|
||||||
# This if is just needed to avoid that tests ever skip way more than
|
|
||||||
# they should for all Python versions.
|
|
||||||
return True
|
|
||||||
|
|
||||||
script = jedi.Script('import typing', environment=environment)
|
|
||||||
return bool(script.infer())
|
return bool(script.infer())
|
||||||
|
|
||||||
|
|
||||||
@@ -145,14 +156,6 @@ def jedi_path():
|
|||||||
return os.path.dirname(__file__)
|
return os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def skip_python2(environment):
|
|
||||||
if environment.version_info.major == 2:
|
|
||||||
# This if is just needed to avoid that tests ever skip way more than
|
|
||||||
# they should for all Python versions.
|
|
||||||
pytest.skip()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def skip_pre_python38(environment):
|
def skip_pre_python38(environment):
|
||||||
if environment.version_info < (3, 8):
|
if environment.version_info < (3, 8):
|
||||||
@@ -167,19 +170,3 @@ def skip_pre_python37(environment):
|
|||||||
# This if is just needed to avoid that tests ever skip way more than
|
# This if is just needed to avoid that tests ever skip way more than
|
||||||
# they should for all Python versions.
|
# they should for all Python versions.
|
||||||
pytest.skip()
|
pytest.skip()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def skip_pre_python35(environment):
|
|
||||||
if environment.version_info < (3, 5):
|
|
||||||
# This if is just needed to avoid that tests ever skip way more than
|
|
||||||
# they should for all Python versions.
|
|
||||||
pytest.skip()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def skip_pre_python36(environment):
|
|
||||||
if environment.version_info < (3, 6):
|
|
||||||
# This if is just needed to avoid that tests ever skip way more than
|
|
||||||
# they should for all Python versions.
|
|
||||||
pytest.skip()
|
|
||||||
+3
-3
@@ -24,10 +24,10 @@ git checkout $BRANCH
|
|||||||
git submodule update --init
|
git submodule update --init
|
||||||
|
|
||||||
# Test first.
|
# Test first.
|
||||||
tox
|
pytest
|
||||||
|
|
||||||
# Create tag
|
# Create tag
|
||||||
tag=v$(python -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)")
|
tag=v$(python3 -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)")
|
||||||
|
|
||||||
master_ref=$(git show-ref -s heads/$BRANCH)
|
master_ref=$(git show-ref -s heads/$BRANCH)
|
||||||
tag_ref=$(git show-ref -s $tag || true)
|
tag_ref=$(git show-ref -s $tag || true)
|
||||||
@@ -44,7 +44,7 @@ fi
|
|||||||
# Package and upload to PyPI
|
# Package and upload to PyPI
|
||||||
#rm -rf dist/ - Not needed anymore, because the folder is never reused.
|
#rm -rf dist/ - Not needed anymore, because the folder is never reused.
|
||||||
echo `pwd`
|
echo `pwd`
|
||||||
python setup.py sdist bdist_wheel
|
python3 setup.py sdist bdist_wheel
|
||||||
# Maybe do a pip install twine before.
|
# Maybe do a pip install twine before.
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
|
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
div.version {
|
||||||
|
color: black !important;
|
||||||
|
margin-top: -1.2em !important;
|
||||||
|
margin-bottom: .6em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.wy-side-nav-search {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
Vendored
-37
@@ -1,37 +0,0 @@
|
|||||||
Copyright (c) 2010 by Armin Ronacher.
|
|
||||||
|
|
||||||
Some rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms of the theme, with or
|
|
||||||
without modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following
|
|
||||||
disclaimer in the documentation and/or other materials provided
|
|
||||||
with the distribution.
|
|
||||||
|
|
||||||
* The names of the contributors may not be used to endorse or
|
|
||||||
promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
We kindly ask you to only use these themes in an unmodified manner just
|
|
||||||
for Flask and Flask-related products, not for unrelated projects. If you
|
|
||||||
like the visual style and want to use it for your own projects, please
|
|
||||||
consider making some larger changes to the themes (such as changing
|
|
||||||
font faces, sizes, colors or margins).
|
|
||||||
|
|
||||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
Vendored
-27
@@ -1,27 +0,0 @@
|
|||||||
{%- extends "basic/layout.html" %}
|
|
||||||
{%- block extrahead %}
|
|
||||||
{{ super() }}
|
|
||||||
{% if theme_touch_icon %}
|
|
||||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
|
||||||
{% endif %}
|
|
||||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
|
||||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
|
||||||
<a href="https://github.com/davidhalter/jedi">
|
|
||||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub">
|
|
||||||
</a>
|
|
||||||
{% endblock %}
|
|
||||||
{%- block relbar2 %}{% endblock %}
|
|
||||||
{% block header %}
|
|
||||||
{{ super() }}
|
|
||||||
{% if pagename == 'index' %}
|
|
||||||
<div class=indexwrapper>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{%- block footer %}
|
|
||||||
<div class="footer">
|
|
||||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
|
||||||
</div>
|
|
||||||
{% if pagename == 'index' %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{%- endblock %}
|
|
||||||
Vendored
-19
@@ -1,19 +0,0 @@
|
|||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
|
||||||
{%- for parent in parents %}
|
|
||||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
|
||||||
{%- endfor %}
|
|
||||||
{%- if prev %}
|
|
||||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
|
||||||
}}">{{ prev.title }}</a></li>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if next %}
|
|
||||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
|
||||||
}}">{{ next.title }}</a></li>
|
|
||||||
{%- endif %}
|
|
||||||
{%- for parent in parents %}
|
|
||||||
</ul></li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
-394
@@ -1,394 +0,0 @@
|
|||||||
/*
|
|
||||||
* flasky.css_t
|
|
||||||
* ~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
|
||||||
* :license: Flask Design License, see LICENSE for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
{% set page_width = '940px' %}
|
|
||||||
{% set sidebar_width = '220px' %}
|
|
||||||
|
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Georgia', serif;
|
|
||||||
font-size: 17px;
|
|
||||||
background-color: white;
|
|
||||||
color: #000;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: {{ page_width }};
|
|
||||||
margin: 30px auto 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 0 0 {{ sidebar_width }};
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
width: {{ sidebar_width }};
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #B1B4B6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #3E4349;
|
|
||||||
padding: 0 30px 0 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.floatingflask {
|
|
||||||
padding: 0 0 10px 10px;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
width: {{ page_width }};
|
|
||||||
margin: 20px auto 30px auto;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #888;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #444;
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a:hover {
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 18px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper p.logo {
|
|
||||||
padding: 0 0 20px 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3,
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-family: 'Garamond', 'Georgia', serif;
|
|
||||||
color: #444;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.logo a,
|
|
||||||
div.sphinxsidebar h3 a,
|
|
||||||
div.sphinxsidebar p.logo a:hover,
|
|
||||||
div.sphinxsidebar h3 a:hover {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p {
|
|
||||||
color: #555;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
font-family: 'Georgia', serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #004B6B;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #6D4100;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1,
|
|
||||||
div.body h2,
|
|
||||||
div.body h3,
|
|
||||||
div.body h4,
|
|
||||||
div.body h5,
|
|
||||||
div.body h6 {
|
|
||||||
font-family: 'Garamond', 'Georgia', serif;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 30px 0px 10px 0px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
{% if theme_index_logo %}
|
|
||||||
div.indexwrapper h1 {
|
|
||||||
text-indent: -999999px;
|
|
||||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
|
||||||
height: {{ theme_index_logo_height }};
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
|
||||||
div.body h2 { font-size: 180%; }
|
|
||||||
div.body h3 { font-size: 150%; }
|
|
||||||
div.body h4 { font-size: 130%; }
|
|
||||||
div.body h5 { font-size: 100%; }
|
|
||||||
div.body h6 { font-size: 100%; }
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #ddd;
|
|
||||||
padding: 0 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li {
|
|
||||||
line-height: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
background: #fafafa;
|
|
||||||
margin: 20px -30px;
|
|
||||||
padding: 10px 30px;
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition tt.xref, div.admonition a tt {
|
|
||||||
border-bottom: 1px solid #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd div.admonition {
|
|
||||||
margin-left: -60px;
|
|
||||||
padding-left: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title {
|
|
||||||
font-family: 'Garamond', 'Georgia', serif;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.last {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.highlight {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, .highlight {
|
|
||||||
background: #FAF3E8;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.note {
|
|
||||||
background-color: #eee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.seealso {
|
|
||||||
background-color: #ffc;
|
|
||||||
border: 1px solid #ff6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, tt {
|
|
||||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.screenshot {
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname, tt.descclassname {
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname {
|
|
||||||
padding-right: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.screenshot {
|
|
||||||
-moz-box-shadow: 2px 2px 4px #eee;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
|
||||||
box-shadow: 2px 2px 4px #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils {
|
|
||||||
border: 1px solid #888;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #eee;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
|
||||||
box-shadow: 2px 2px 4px #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils td, table.docutils th {
|
|
||||||
border: 1px solid #888;
|
|
||||||
padding: 0.25em 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list, table.footnote {
|
|
||||||
border: none;
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote {
|
|
||||||
margin: 15px 0;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
background: #fdfdfd;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote + table.footnote {
|
|
||||||
margin-top: -15px;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list th {
|
|
||||||
padding: 0 0.8em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list td {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td.label {
|
|
||||||
width: 0px;
|
|
||||||
padding: 0.3em 0 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl dd {
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 0 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul, ol {
|
|
||||||
margin: 10px 0 10px 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: #eee;
|
|
||||||
padding: 7px 30px;
|
|
||||||
margin: 15px -30px;
|
|
||||||
line-height: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl pre, blockquote pre, li pre {
|
|
||||||
margin-left: -60px;
|
|
||||||
padding-left: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl dl pre {
|
|
||||||
margin-left: -90px;
|
|
||||||
padding-left: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt {
|
|
||||||
background-color: #ecf0f3;
|
|
||||||
color: #222;
|
|
||||||
/* padding: 1px 2px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.xref, a tt {
|
|
||||||
background-color: #FBFBFB;
|
|
||||||
border-bottom: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.reference {
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted #004B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.reference:hover {
|
|
||||||
border-bottom: 1px solid #6D4100;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.footnote-reference {
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.7em;
|
|
||||||
vertical-align: top;
|
|
||||||
border-bottom: 1px dotted #004B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.footnote-reference:hover {
|
|
||||||
border-bottom: 1px solid #6D4100;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover tt {
|
|
||||||
background: #EEE;
|
|
||||||
}
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* small_flask.css_t
|
|
||||||
* ~~~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
|
||||||
* :license: Flask Design License, see LICENSE for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: none;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
display: block;
|
|
||||||
float: none;
|
|
||||||
width: 102.5%;
|
|
||||||
margin: 50px -30px -20px -30px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: #333;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.logo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 10px 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul,
|
|
||||||
div.related ul li {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
min-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
Vendored
-9
@@ -1,9 +0,0 @@
|
|||||||
[theme]
|
|
||||||
inherit = basic
|
|
||||||
stylesheet = flasky.css
|
|
||||||
pygments_style = flask_theme_support.FlaskyStyle
|
|
||||||
|
|
||||||
[options]
|
|
||||||
index_logo =
|
|
||||||
index_logo_height = 120px
|
|
||||||
touch_icon =
|
|
||||||
Vendored
-125
@@ -1,125 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) 2010 by Armin Ronacher.
|
|
||||||
|
|
||||||
Some rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms of the theme, with or
|
|
||||||
without modification, are permitted provided that the following conditions
|
|
||||||
are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following
|
|
||||||
disclaimer in the documentation and/or other materials provided
|
|
||||||
with the distribution.
|
|
||||||
|
|
||||||
* The names of the contributors may not be used to endorse or
|
|
||||||
promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
We kindly ask you to only use these themes in an unmodified manner just
|
|
||||||
for Flask and Flask-related products, not for unrelated projects. If you
|
|
||||||
like the visual style and want to use it for your own projects, please
|
|
||||||
consider making some larger changes to the themes (such as changing
|
|
||||||
font faces, sizes, colors or margins).
|
|
||||||
|
|
||||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
"""
|
|
||||||
# flasky extensions. flasky pygments style based on tango style
|
|
||||||
from pygments.style import Style
|
|
||||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
|
||||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskyStyle(Style):
|
|
||||||
background_color = "#f8f8f8"
|
|
||||||
default_style = ""
|
|
||||||
|
|
||||||
styles = {
|
|
||||||
# No corresponding class for the following:
|
|
||||||
#Text: "", # class: ''
|
|
||||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
|
||||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
|
||||||
Other: "#000000", # class 'x'
|
|
||||||
|
|
||||||
Comment: "italic #8f5902", # class: 'c'
|
|
||||||
Comment.Preproc: "noitalic", # class: 'cp'
|
|
||||||
|
|
||||||
Keyword: "bold #004461", # class: 'k'
|
|
||||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
|
||||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
|
||||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
|
||||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
|
||||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
|
||||||
Keyword.Type: "bold #004461", # class: 'kt'
|
|
||||||
|
|
||||||
Operator: "#582800", # class: 'o'
|
|
||||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
|
||||||
|
|
||||||
Punctuation: "bold #000000", # class: 'p'
|
|
||||||
|
|
||||||
# because special names such as Name.Class, Name.Function, etc.
|
|
||||||
# are not recognized as such later in the parsing, we choose them
|
|
||||||
# to look the same as ordinary variables.
|
|
||||||
Name: "#000000", # class: 'n'
|
|
||||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
|
||||||
Name.Builtin: "#004461", # class: 'nb'
|
|
||||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
|
||||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
|
||||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
|
||||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
|
||||||
Name.Entity: "#ce5c00", # class: 'ni'
|
|
||||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
|
||||||
Name.Function: "#000000", # class: 'nf'
|
|
||||||
Name.Property: "#000000", # class: 'py'
|
|
||||||
Name.Label: "#f57900", # class: 'nl'
|
|
||||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
|
||||||
Name.Other: "#000000", # class: 'nx'
|
|
||||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
|
||||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
|
||||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
|
||||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
|
||||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
|
||||||
|
|
||||||
Number: "#990000", # class: 'm'
|
|
||||||
|
|
||||||
Literal: "#000000", # class: 'l'
|
|
||||||
Literal.Date: "#000000", # class: 'ld'
|
|
||||||
|
|
||||||
String: "#4e9a06", # class: 's'
|
|
||||||
String.Backtick: "#4e9a06", # class: 'sb'
|
|
||||||
String.Char: "#4e9a06", # class: 'sc'
|
|
||||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
|
||||||
String.Double: "#4e9a06", # class: 's2'
|
|
||||||
String.Escape: "#4e9a06", # class: 'se'
|
|
||||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
|
||||||
String.Interpol: "#4e9a06", # class: 'si'
|
|
||||||
String.Other: "#4e9a06", # class: 'sx'
|
|
||||||
String.Regex: "#4e9a06", # class: 'sr'
|
|
||||||
String.Single: "#4e9a06", # class: 's1'
|
|
||||||
String.Symbol: "#4e9a06", # class: 'ss'
|
|
||||||
|
|
||||||
Generic: "#000000", # class: 'g'
|
|
||||||
Generic.Deleted: "#a40000", # class: 'gd'
|
|
||||||
Generic.Emph: "italic #000000", # class: 'ge'
|
|
||||||
Generic.Error: "#ef2929", # class: 'gr'
|
|
||||||
Generic.Heading: "bold #000080", # class: 'gh'
|
|
||||||
Generic.Inserted: "#00A000", # class: 'gi'
|
|
||||||
Generic.Output: "#888", # class: 'go'
|
|
||||||
Generic.Prompt: "#745334", # class: 'gp'
|
|
||||||
Generic.Strong: "bold #000000", # class: 'gs'
|
|
||||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
|
||||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
|
||||||
}
|
|
||||||
+26
-23
@@ -1,5 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Jedi documentation build configuration file, created by
|
# Jedi documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Wed Dec 26 00:11:34 2012.
|
# sphinx-quickstart on Wed Dec 26 00:11:34 2012.
|
||||||
#
|
#
|
||||||
@@ -13,13 +11,11 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import datetime
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
sys.path.append(os.path.abspath('_themes'))
|
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
@@ -29,7 +25,8 @@ sys.path.append(os.path.abspath('_themes'))
|
|||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
|
||||||
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram']
|
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram',
|
||||||
|
'sphinx_rtd_theme', 'sphinx.ext.autosummary']
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
@@ -44,8 +41,8 @@ source_encoding = 'utf-8'
|
|||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Jedi'
|
project = 'Jedi'
|
||||||
copyright = u'jedi contributors'
|
copyright = 'jedi contributors'
|
||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi.utils import version_info
|
from jedi.utils import version_info
|
||||||
@@ -54,8 +51,8 @@ from jedi.utils import version_info
|
|||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y.Z version.
|
||||||
version = '.'.join(str(x) for x in version_info()[:2])
|
version = '.'.join(str(x) for x in version_info()[:3])
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = jedi.__version__
|
release = jedi.__version__
|
||||||
|
|
||||||
@@ -98,12 +95,15 @@ pygments_style = 'sphinx'
|
|||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'flask'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
html_theme_options = {
|
||||||
|
'logo_only': True,
|
||||||
|
'style_nav_header_background': 'white',
|
||||||
|
}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
html_theme_path = ['_themes']
|
html_theme_path = ['_themes']
|
||||||
@@ -117,7 +117,7 @@ html_theme_path = ['_themes']
|
|||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
#html_logo = None
|
html_logo = '_static/logo.png'
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
@@ -129,6 +129,8 @@ html_theme_path = ['_themes']
|
|||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
html_css_files = ['custom_style.css']
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
@@ -145,7 +147,7 @@ html_sidebars = {
|
|||||||
#'relations.html',
|
#'relations.html',
|
||||||
'ghbuttons.html',
|
'ghbuttons.html',
|
||||||
#'sourcelink.html',
|
#'sourcelink.html',
|
||||||
#'searchbox.html'
|
'searchbox.html'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +165,13 @@ html_sidebars = {
|
|||||||
#html_split_index = False
|
#html_split_index = False
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
# If true, links to the reST sources are added to the pages.
|
||||||
#html_show_sourcelink = True
|
html_show_sourcelink = False
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
#html_show_sphinx = True
|
html_show_sphinx = False
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
#html_show_copyright = True
|
html_show_copyright = False
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
@@ -201,8 +203,8 @@ latex_elements = {
|
|||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'Jedi.tex', u'Jedi Documentation',
|
('index', 'Jedi.tex', 'Jedi Documentation',
|
||||||
u'Jedi contributors', 'manual'),
|
'Jedi contributors', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
@@ -231,8 +233,8 @@ latex_documents = [
|
|||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'jedi', u'Jedi Documentation',
|
('index', 'jedi', 'Jedi Documentation',
|
||||||
[u'Jedi contributors'], 1)
|
['Jedi contributors'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
@@ -245,8 +247,8 @@ man_pages = [
|
|||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'Jedi', u'Jedi Documentation',
|
('index', 'Jedi', 'Jedi Documentation',
|
||||||
u'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.',
|
'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -274,7 +276,8 @@ autodoc_default_flags = []
|
|||||||
# -- Options for intersphinx module --------------------------------------------
|
# -- Options for intersphinx module --------------------------------------------
|
||||||
|
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'https://docs.python.org/': None,
|
'python': ('https://docs.python.org/', None),
|
||||||
|
'parso': ('https://parso.readthedocs.io/en/latest/', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
.. include global.rst
|
||||||
|
|
||||||
|
History & Acknowledgements
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Acknowledgements
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- Dave Halter for creating and maintaining Jedi & Parso.
|
||||||
|
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of
|
||||||
|
other things.
|
||||||
|
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
|
||||||
|
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
|
||||||
|
(originally used in lib2to3).
|
||||||
|
- Thanks to all the :ref:`contributors <contributors>`.
|
||||||
|
|
||||||
|
A Little Bit of History
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Written by Dave.
|
||||||
|
|
||||||
|
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
|
||||||
|
of the precognition the Jedi have. There's even an awesome `scene
|
||||||
|
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
|
||||||
|
|
||||||
|
But actually the name has not much to do with Star Wars. It's part of my
|
||||||
|
second name Jedidjah.
|
||||||
|
|
||||||
|
I actually started Jedi back in 2012, because there were no good solutions
|
||||||
|
available for VIM. Most auto-completion solutions just did not work well. The
|
||||||
|
only good solution was PyCharm. But I liked my good old VIM very much. There
|
||||||
|
was also a solution called Rope that did not work at all for me. So I decided
|
||||||
|
to write my own version of a completion engine.
|
||||||
|
|
||||||
|
The first idea was to execute non-dangerous code. But I soon realized, that
|
||||||
|
this would not work. So I started to build a static analysis tool.
|
||||||
|
The biggest problem that I had at the time was that I did not know a thing
|
||||||
|
about parsers.I did not did not even know the word static analysis. It turns
|
||||||
|
out they are the foundation of a good static analysis tool. I of course did not
|
||||||
|
know that and tried to write my own poor version of a parser that I ended up
|
||||||
|
throwing away two years later.
|
||||||
|
|
||||||
|
Because of my lack of knowledge, everything after 2012 and before 2020 was
|
||||||
|
basically refactoring. I rewrote the core parts of Jedi probably like 5-10
|
||||||
|
times. The last big rewrite (that I did twice) was the inclusion of
|
||||||
|
gradual typing and stubs.
|
||||||
|
|
||||||
|
I learned during that time that it is crucial to have a good understanding of
|
||||||
|
your problem. Otherwise you just end up doing it again. I only wrote features
|
||||||
|
in the beginning and in the end. Everything else was bugfixing and refactoring.
|
||||||
|
However now I am really happy with the result. It works well, bugfixes can be
|
||||||
|
quick and is pretty much feature complete.
|
||||||
|
|
||||||
|
--------
|
||||||
|
|
||||||
|
I will leave you with a small annectote that happend in 2012, if I remember
|
||||||
|
correctly. After I explained Guido van Rossum, how some parts of my
|
||||||
|
auto-completion work, he said:
|
||||||
|
|
||||||
|
*"Oh, that worries me..."*
|
||||||
|
|
||||||
|
Now that it is finished, I hope he likes it :-).
|
||||||
|
|
||||||
|
.. _contributors:
|
||||||
|
|
||||||
|
.. include:: ../../AUTHORS.txt
|
||||||
@@ -5,6 +5,49 @@
|
|||||||
API Return Classes
|
API Return Classes
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
.. automodule:: jedi.api.classes
|
Abstract Base Class
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
.. autoclass:: jedi.api.classes.BaseName
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:show-inheritance:
|
||||||
|
|
||||||
|
Name
|
||||||
|
~~~~
|
||||||
|
.. autoclass:: jedi.api.classes.Name
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Completion
|
||||||
|
~~~~~~~~~~
|
||||||
|
.. autoclass:: jedi.api.classes.Completion
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
BaseSignature
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
.. autoclass:: jedi.api.classes.BaseSignature
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Signature
|
||||||
|
~~~~~~~~~
|
||||||
|
.. autoclass:: jedi.api.classes.Signature
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
ParamName
|
||||||
|
~~~~~~~~~
|
||||||
|
.. autoclass:: jedi.api.classes.ParamName
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Refactoring
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: jedi.api.refactoring.Refactoring
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: jedi.api.errors.SyntaxError
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
+98
-54
@@ -3,56 +3,74 @@
|
|||||||
API Overview
|
API Overview
|
||||||
============
|
============
|
||||||
|
|
||||||
.. currentmodule:: jedi
|
.. note:: This documentation is mostly for Plugin developers, who want to
|
||||||
|
improve their editors/IDE with Jedi.
|
||||||
Note: This documentation is for Plugin developers, who want to improve their
|
|
||||||
editors/IDE autocompletion
|
|
||||||
|
|
||||||
If you want to use |jedi|, you first need to ``import jedi``. You then have
|
|
||||||
direct access to the :class:`.Script`. You can then call the functions
|
|
||||||
documented here. These functions return :ref:`API classes
|
|
||||||
<api-classes>`.
|
|
||||||
|
|
||||||
|
|
||||||
Deprecations
|
|
||||||
------------
|
|
||||||
|
|
||||||
The deprecation process is as follows:
|
|
||||||
|
|
||||||
1. A deprecation is announced in the next major/minor release.
|
|
||||||
2. We wait either at least a year & at least two minor releases until we remove
|
|
||||||
the deprecated functionality.
|
|
||||||
|
|
||||||
|
|
||||||
API Documentation
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The API consists of a few different parts:
|
|
||||||
|
|
||||||
- The main starting points for complete/goto: :class:`.Script` and :class:`.Interpreter`
|
|
||||||
- Helpful functions: :func:`.preload_module` and :func:`.set_debug_function`
|
|
||||||
- :ref:`API Result Classes <api-classes>`
|
|
||||||
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
|
|
||||||
:func:`.find_system_environments` and :func:`.find_virtualenvs`
|
|
||||||
|
|
||||||
.. _api:
|
.. _api:
|
||||||
|
|
||||||
Static Analysis Interface
|
The API consists of a few different parts:
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. automodule:: jedi
|
- The main starting points for complete/goto: :class:`.Script` and
|
||||||
|
:class:`.Interpreter`. If you work with Jedi you want to understand these
|
||||||
|
classes first.
|
||||||
|
- :ref:`API Result Classes <api-classes>`
|
||||||
|
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
|
||||||
|
:func:`.find_system_environments` and :func:`.find_virtualenvs`
|
||||||
|
- A way to work with different :ref:`Folders / Projects <projects>`
|
||||||
|
- Helpful functions: :func:`.preload_module` and :func:`.set_debug_function`
|
||||||
|
|
||||||
|
The methods that you are most likely going to use to work with Jedi are the
|
||||||
|
following ones:
|
||||||
|
|
||||||
|
.. currentmodule:: jedi
|
||||||
|
|
||||||
|
.. autosummary::
|
||||||
|
:nosignatures:
|
||||||
|
|
||||||
|
Script.complete
|
||||||
|
Script.goto
|
||||||
|
Script.infer
|
||||||
|
Script.help
|
||||||
|
Script.get_signatures
|
||||||
|
Script.get_references
|
||||||
|
Script.get_context
|
||||||
|
Script.get_names
|
||||||
|
Script.get_syntax_errors
|
||||||
|
Script.rename
|
||||||
|
Script.inline
|
||||||
|
Script.extract_variable
|
||||||
|
Script.extract_function
|
||||||
|
Script.search
|
||||||
|
Script.complete_search
|
||||||
|
Project.search
|
||||||
|
Project.complete_search
|
||||||
|
|
||||||
|
Script
|
||||||
|
------
|
||||||
|
|
||||||
.. autoclass:: jedi.Script
|
.. autoclass:: jedi.Script
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Interpreter
|
||||||
|
-----------
|
||||||
.. autoclass:: jedi.Interpreter
|
.. autoclass:: jedi.Interpreter
|
||||||
:members:
|
:members:
|
||||||
.. autofunction:: jedi.preload_module
|
|
||||||
.. autofunction:: jedi.set_debug_function
|
.. _projects:
|
||||||
|
|
||||||
|
Projects
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. automodule:: jedi.api.project
|
||||||
|
|
||||||
|
.. autofunction:: jedi.get_default_project
|
||||||
|
.. autoclass:: jedi.Project
|
||||||
|
:members:
|
||||||
|
|
||||||
.. _environments:
|
.. _environments:
|
||||||
|
|
||||||
Environments
|
Environments
|
||||||
~~~~~~~~~~~~
|
------------
|
||||||
|
|
||||||
.. automodule:: jedi.api.environment
|
.. automodule:: jedi.api.environment
|
||||||
|
|
||||||
@@ -65,18 +83,31 @@ Environments
|
|||||||
.. autoclass:: jedi.api.environment.Environment
|
.. autoclass:: jedi.api.environment.Environment
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Helper Functions
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. autofunction:: jedi.preload_module
|
||||||
|
.. autofunction:: jedi.set_debug_function
|
||||||
|
|
||||||
|
Errors
|
||||||
|
------
|
||||||
|
|
||||||
|
.. autoexception:: jedi.InternalError
|
||||||
|
.. autoexception:: jedi.RefactoringError
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Completions:
|
Completions
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
>>> import jedi
|
>>> import jedi
|
||||||
>>> source = '''import json; json.l'''
|
>>> code = '''import json; json.l'''
|
||||||
>>> script = jedi.Script(source, path='')
|
>>> script = jedi.Script(code, path='example.py')
|
||||||
>>> script
|
>>> script
|
||||||
<jedi.api.Script object at 0x2121b10>
|
<Script: 'example.py' <SameEnvironment: 3.9.0 in /usr>>
|
||||||
>>> completions = script.complete(1, 19)
|
>>> completions = script.complete(1, 19)
|
||||||
>>> completions
|
>>> completions
|
||||||
[<Completion: load>, <Completion: loads>]
|
[<Completion: load>, <Completion: loads>]
|
||||||
@@ -87,12 +118,14 @@ Completions:
|
|||||||
>>> completions[1].name
|
>>> completions[1].name
|
||||||
'loads'
|
'loads'
|
||||||
|
|
||||||
Definitions / Goto:
|
Type Inference / Goto
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
>>> import jedi
|
>>> import jedi
|
||||||
>>> source = '''def my_func():
|
>>> code = '''\
|
||||||
|
... def my_func():
|
||||||
... print 'called'
|
... print 'called'
|
||||||
...
|
...
|
||||||
... alias = my_func
|
... alias = my_func
|
||||||
@@ -100,30 +133,41 @@ Definitions / Goto:
|
|||||||
... inception = my_list[2]
|
... inception = my_list[2]
|
||||||
...
|
...
|
||||||
... inception()'''
|
... inception()'''
|
||||||
>>> script = jedi.Script(source, path='')
|
>>> script = jedi.Script(code)
|
||||||
>>>
|
>>>
|
||||||
>>> script.goto(8, 1)
|
>>> script.goto(8, 1)
|
||||||
[<Definition inception=my_list[2]>]
|
[<Name full_name='__main__.inception', description='inception = my_list[2]'>]
|
||||||
>>>
|
>>>
|
||||||
>>> script.infer(8, 1)
|
>>> script.infer(8, 1)
|
||||||
[<Definition def my_func>]
|
[<Name full_name='__main__.my_func', description='def my_func'>]
|
||||||
|
|
||||||
References:
|
References
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
>>> import jedi
|
>>> import jedi
|
||||||
>>> source = '''x = 3
|
>>> code = '''\
|
||||||
|
... x = 3
|
||||||
... if 1 == 2:
|
... if 1 == 2:
|
||||||
... x = 4
|
... x = 4
|
||||||
... else:
|
... else:
|
||||||
... del x'''
|
... del x'''
|
||||||
>>> script = jedi.Script(source, '')
|
>>> script = jedi.Script(code)
|
||||||
>>> rns = script.get_references(5, 8)
|
>>> rns = script.get_references(5, 8)
|
||||||
>>> rns
|
>>> rns
|
||||||
[<Definition full_name='__main__.x', description='x = 3'>,
|
[<Name full_name='__main__.x', description='x = 3'>,
|
||||||
<Definition full_name='__main__.x', description='x'>]
|
<Name full_name='__main__.x', description='x = 4'>,
|
||||||
|
<Name full_name='__main__.x', description='del x'>]
|
||||||
>>> rns[1].line
|
>>> rns[1].line
|
||||||
5
|
3
|
||||||
>>> rns[0].column
|
>>> rns[1].column
|
||||||
8
|
4
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
------------
|
||||||
|
|
||||||
|
The deprecation process is as follows:
|
||||||
|
|
||||||
|
1. A deprecation is announced in any release.
|
||||||
|
2. The next major release removes the deprecated functionality.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../CHANGELOG.rst
|
||||||
+27
-30
@@ -22,16 +22,12 @@ couldn't get rid of complexity. I know that **simple is better than complex**,
|
|||||||
but unfortunately it sometimes requires complex solutions to understand complex
|
but unfortunately it sometimes requires complex solutions to understand complex
|
||||||
systems.
|
systems.
|
||||||
|
|
||||||
Since most of the Jedi internals have been written by me (David Halter), this
|
In six chapters I'm trying to describe the internals of |jedi|:
|
||||||
introduction will be written mostly by me, because no one else understands to
|
|
||||||
the same level how Jedi works. Actually this is also the reason for exactly this
|
|
||||||
part of the documentation. To make multiple people able to edit the Jedi core.
|
|
||||||
|
|
||||||
In five chapters I'm trying to describe the internals of |jedi|:
|
|
||||||
|
|
||||||
- :ref:`The Jedi Core <core>`
|
- :ref:`The Jedi Core <core>`
|
||||||
- :ref:`Core Extensions <core-extensions>`
|
- :ref:`Core Extensions <core-extensions>`
|
||||||
- :ref:`Imports & Modules <imports-modules>`
|
- :ref:`Imports & Modules <imports-modules>`
|
||||||
|
- :ref:`Stubs & Annotations <stubs>`
|
||||||
- :ref:`Caching & Recursions <caching-recursions>`
|
- :ref:`Caching & Recursions <caching-recursions>`
|
||||||
- :ref:`Helper modules <dev-helpers>`
|
- :ref:`Helper modules <dev-helpers>`
|
||||||
|
|
||||||
@@ -59,17 +55,17 @@ because that's where all the magic happens. I need to introduce the :ref:`parser
|
|||||||
Parser
|
Parser
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Jedi used to have it's internal parser, however this is now a separate project
|
Jedi used to have its internal parser, however this is now a separate project
|
||||||
and is called `parso <http://parso.readthedocs.io>`_.
|
and is called `parso <http://parso.readthedocs.io>`_.
|
||||||
|
|
||||||
The parser creates a syntax tree that |jedi| analyses and tries to understand.
|
The parser creates a syntax tree that |jedi| analyses and tries to understand.
|
||||||
The grammar that this parsers uses is very similar to the official Python
|
The grammar that this parser uses is very similar to the official Python
|
||||||
`grammar files <https://docs.python.org/3/reference/grammar.html>`_.
|
`grammar files <https://docs.python.org/3/reference/grammar.html>`_.
|
||||||
|
|
||||||
.. _inference:
|
.. _inference:
|
||||||
|
|
||||||
Type inference of python code (inference/__init__.py)
|
Type inference of python code (inference/__init__.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: jedi.inference
|
.. automodule:: jedi.inference
|
||||||
|
|
||||||
@@ -80,7 +76,7 @@ Inference Values (inference/base_value.py)
|
|||||||
|
|
||||||
.. inheritance-diagram::
|
.. inheritance-diagram::
|
||||||
jedi.inference.value.instance.TreeInstance
|
jedi.inference.value.instance.TreeInstance
|
||||||
jedi.inference.value.klass.Classvalue
|
jedi.inference.value.klass.ClassValue
|
||||||
jedi.inference.value.function.FunctionValue
|
jedi.inference.value.function.FunctionValue
|
||||||
jedi.inference.value.function.FunctionExecutionContext
|
jedi.inference.value.function.FunctionExecutionContext
|
||||||
:parts: 1
|
:parts: 1
|
||||||
@@ -89,7 +85,7 @@ Inference Values (inference/base_value.py)
|
|||||||
.. _name_resolution:
|
.. _name_resolution:
|
||||||
|
|
||||||
Name resolution (inference/finder.py)
|
Name resolution (inference/finder.py)
|
||||||
++++++++++++++++++++++++++++++++++++
|
+++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
.. automodule:: jedi.inference.finder
|
.. automodule:: jedi.inference.finder
|
||||||
|
|
||||||
@@ -114,7 +110,7 @@ Core Extensions
|
|||||||
Core Extensions is a summary of the following topics:
|
Core Extensions is a summary of the following topics:
|
||||||
|
|
||||||
- :ref:`Iterables & Dynamic Arrays <iterables>`
|
- :ref:`Iterables & Dynamic Arrays <iterables>`
|
||||||
- :ref:`Dynamic Parameters <dynamic>`
|
- :ref:`Dynamic Parameters <dynamic_params>`
|
||||||
- :ref:`Docstrings <docstrings>`
|
- :ref:`Docstrings <docstrings>`
|
||||||
- :ref:`Refactoring <refactoring>`
|
- :ref:`Refactoring <refactoring>`
|
||||||
|
|
||||||
@@ -125,7 +121,7 @@ without some features.
|
|||||||
.. _iterables:
|
.. _iterables:
|
||||||
|
|
||||||
Iterables & Dynamic Arrays (inference/value/iterable.py)
|
Iterables & Dynamic Arrays (inference/value/iterable.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To understand Python on a deeper level, |jedi| needs to understand some of the
|
To understand Python on a deeper level, |jedi| needs to understand some of the
|
||||||
dynamic features of Python like lists that are filled after creation:
|
dynamic features of Python like lists that are filled after creation:
|
||||||
@@ -133,33 +129,33 @@ dynamic features of Python like lists that are filled after creation:
|
|||||||
.. automodule:: jedi.inference.value.iterable
|
.. automodule:: jedi.inference.value.iterable
|
||||||
|
|
||||||
|
|
||||||
.. _dynamic:
|
.. _dynamic_params:
|
||||||
|
|
||||||
Parameter completion (inference/dynamic.py)
|
Parameter completion (inference/dynamic_params.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: jedi.inference.dynamic
|
.. automodule:: jedi.inference.dynamic_params
|
||||||
|
|
||||||
|
|
||||||
.. _docstrings:
|
.. _docstrings:
|
||||||
|
|
||||||
Docstrings (inference/docstrings.py)
|
Docstrings (inference/docstrings.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: jedi.inference.docstrings
|
.. automodule:: jedi.inference.docstrings
|
||||||
|
|
||||||
.. _refactoring:
|
.. _refactoring:
|
||||||
|
|
||||||
Refactoring (inference/refactoring.py)
|
Refactoring (api/refactoring.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: jedi.refactoring
|
.. automodule:: jedi.api.refactoring
|
||||||
|
|
||||||
|
|
||||||
.. _imports-modules:
|
.. _imports-modules:
|
||||||
|
|
||||||
Imports & Modules
|
Imports & Modules
|
||||||
-------------------
|
-----------------
|
||||||
|
|
||||||
|
|
||||||
- :ref:`Modules <modules>`
|
- :ref:`Modules <modules>`
|
||||||
@@ -170,7 +166,7 @@ Imports & Modules
|
|||||||
.. _builtin:
|
.. _builtin:
|
||||||
|
|
||||||
Compiled Modules (inference/compiled.py)
|
Compiled Modules (inference/compiled.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: jedi.inference.compiled
|
.. automodule:: jedi.inference.compiled
|
||||||
|
|
||||||
@@ -178,10 +174,16 @@ Compiled Modules (inference/compiled.py)
|
|||||||
.. _imports:
|
.. _imports:
|
||||||
|
|
||||||
Imports (inference/imports.py)
|
Imports (inference/imports.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: jedi.inference.imports
|
.. automodule:: jedi.inference.imports
|
||||||
|
|
||||||
|
.. _stubs:
|
||||||
|
|
||||||
|
Stubs & Annotations (inference/gradual)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. automodule:: jedi.inference.gradual
|
||||||
|
|
||||||
.. _caching-recursions:
|
.. _caching-recursions:
|
||||||
|
|
||||||
@@ -210,13 +212,8 @@ Recursions (recursion.py)
|
|||||||
.. _dev-helpers:
|
.. _dev-helpers:
|
||||||
|
|
||||||
Helper Modules
|
Helper Modules
|
||||||
---------------
|
--------------
|
||||||
|
|
||||||
Most other modules are not really central to how Jedi works. They all contain
|
Most other modules are not really central to how Jedi works. They all contain
|
||||||
relevant code, but you if you understand the modules above, you pretty much
|
relevant code, but you if you understand the modules above, you pretty much
|
||||||
understand Jedi.
|
understand Jedi.
|
||||||
|
|
||||||
Python 2/3 compatibility (_compatibility.py)
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. automodule:: jedi._compatibility
|
|
||||||
+62
-188
@@ -1,29 +1,30 @@
|
|||||||
.. include:: ../global.rst
|
.. include:: ../global.rst
|
||||||
|
|
||||||
Features and Caveats
|
Features and Limitations
|
||||||
====================
|
========================
|
||||||
|
|
||||||
Jedi obviously supports autocompletion. It's also possible to get it working in
|
Jedi's main API calls and features are:
|
||||||
(:ref:`your REPL (IPython, etc.) <repl-completion>`).
|
|
||||||
|
|
||||||
Static analysis is also possible by using ``jedi.Script(...).get_names``.
|
- Autocompletion: :meth:`.Script.complete`; It's also possible to get it
|
||||||
|
working in :ref:`your REPL (IPython, etc.) <repl-completion>`
|
||||||
|
- Goto/Type Inference: :meth:`.Script.goto` and :meth:`.Script.infer`
|
||||||
|
- Static Analysis: :meth:`.Script.get_names` and :meth:`.Script.get_syntax_errors`
|
||||||
|
- Refactorings: :meth:`.Script.rename`, :meth:`.Script.inline`,
|
||||||
|
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`
|
||||||
|
- Code Search: :meth:`.Script.search` and :meth:`.Project.search`
|
||||||
|
|
||||||
Jedi would in theory support refactoring, but we have never publicized it,
|
Basic Features
|
||||||
because it's not production ready. If you're interested in helping out here,
|
--------------
|
||||||
let me know. With the latest parser changes, it should be very easy to actually
|
|
||||||
make it work.
|
|
||||||
|
|
||||||
|
- Python 3.6+ support
|
||||||
General Features
|
|
||||||
----------------
|
|
||||||
|
|
||||||
- Python 2.7 and 3.4+ support
|
|
||||||
- Ignores syntax errors and wrong indentation
|
- Ignores syntax errors and wrong indentation
|
||||||
- Can deal with complex module / function / class structures
|
- Can deal with complex module / function / class structures
|
||||||
- Great Virtualenv support
|
- Great ``virtualenv``/``venv`` support
|
||||||
- Can infer function arguments from sphinx, epydoc and basic numpydoc docstrings,
|
- Works great with Python's :ref:`type hinting <type-hinting>`,
|
||||||
and PEP0484-style type hints (:ref:`type hinting <type-hinting>`)
|
- Understands stub files
|
||||||
- Stub files
|
- Can infer function arguments for sphinx, epydoc and basic numpydoc docstrings
|
||||||
|
- Is overall a very solid piece of software that has been refined for a long
|
||||||
|
time. Bug reports are very welcome and are usually fixed within a few weeks.
|
||||||
|
|
||||||
|
|
||||||
Supported Python Features
|
Supported Python Features
|
||||||
@@ -38,7 +39,7 @@ Supported Python Features
|
|||||||
- ``*args`` / ``**kwargs``
|
- ``*args`` / ``**kwargs``
|
||||||
- decorators / lambdas / closures
|
- decorators / lambdas / closures
|
||||||
- generators / iterators
|
- generators / iterators
|
||||||
- some descriptors: property / staticmethod / classmethod
|
- descriptors: property / staticmethod / classmethod / custom descriptors
|
||||||
- some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``,
|
- some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``,
|
||||||
``__getitem__``, ``__init__``
|
``__getitem__``, ``__init__``
|
||||||
- ``list.append()``, ``set.add()``, ``list.extend()``, etc.
|
- ``list.append()``, ``set.add()``, ``list.extend()``, etc.
|
||||||
@@ -46,191 +47,64 @@ Supported Python Features
|
|||||||
- relative imports
|
- relative imports
|
||||||
- ``getattr()`` / ``__getattr__`` / ``__getattribute__``
|
- ``getattr()`` / ``__getattr__`` / ``__getattribute__``
|
||||||
- function annotations
|
- function annotations
|
||||||
- class decorators (py3k feature, are being ignored too, until I find a use
|
- simple/typical ``sys.path`` modifications
|
||||||
case, that doesn't work with |jedi|)
|
|
||||||
- simple/usual ``sys.path`` modifications
|
|
||||||
- ``isinstance`` checks for if/while/assert
|
- ``isinstance`` checks for if/while/assert
|
||||||
- namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces)
|
- namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces)
|
||||||
- Django / Flask / Buildout support
|
- Django / Flask / Buildout support
|
||||||
- Understands Pytest fixtures
|
- Understands Pytest fixtures
|
||||||
|
|
||||||
|
|
||||||
Not Supported
|
Limitations
|
||||||
-------------
|
-----------
|
||||||
|
|
||||||
Not yet implemented:
|
In general Jedi's limit are quite high, but for very big projects or very
|
||||||
|
complex code, sometimes Jedi intentionally stops type inference, to avoid
|
||||||
|
hanging for a long time.
|
||||||
|
|
||||||
- manipulations of instances outside the instance variables without using
|
Additionally there are some Python patterns Jedi does not support. This is
|
||||||
methods
|
intentional and below should be a complete list:
|
||||||
|
|
||||||
Will probably never be implemented:
|
- Arbitrary metaclasses: Some metaclasses like enums and dataclasses are
|
||||||
|
reimplemented in Jedi to make them work. Most of the time stubs are good
|
||||||
- metaclasses (how could an auto-completion ever support this)
|
enough to get type inference working, even when metaclasses are involved.
|
||||||
- ``setattr()``, ``__import__()``
|
- ``setattr()``, ``__import__()``
|
||||||
- writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
|
- Writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
|
||||||
|
- Manipulations of instances outside the instance variables without using
|
||||||
|
methods
|
||||||
|
|
||||||
|
Performance Issues
|
||||||
Caveats
|
~~~~~~~~~~~~~~~~~~
|
||||||
-------
|
|
||||||
|
|
||||||
**Slow Performance**
|
|
||||||
|
|
||||||
Importing ``numpy`` can be quite slow sometimes, as well as loading the
|
Importing ``numpy`` can be quite slow sometimes, as well as loading the
|
||||||
builtins the first time. If you want to speed things up, you could write import
|
builtins the first time. If you want to speed things up, you could preload
|
||||||
hooks in |jedi|, which preload stuff. However, once loaded, this is not a
|
libriaries in |jedi|, with :func:`.preload_module`. However, once loaded, this
|
||||||
problem anymore. The same is true for huge modules like ``PySide``, ``wx``,
|
should not be a problem anymore. The same is true for huge modules like
|
||||||
etc.
|
``PySide``, ``wx``, ``tensorflow``, ``pandas``, etc.
|
||||||
|
|
||||||
**Security**
|
Jedi does not have a very good cache layer. This is probably the biggest and
|
||||||
|
only architectural `issue <https://github.com/davidhalter/jedi/issues/1059>`_ in
|
||||||
|
Jedi. Unfortunately it is not easy to change that. Dave Halter is thinking
|
||||||
|
about rewriting Jedi in Rust, but it has taken Jedi more than 8 years to reach
|
||||||
|
version 1.0, a rewrite will probably also take years.
|
||||||
|
|
||||||
Security is an important issue for |jedi|. Therefore no Python code is
|
Security
|
||||||
executed. As long as you write pure Python, everything is inferred
|
--------
|
||||||
statically. But: If you use builtin modules (``c_builtin``) there is no other
|
|
||||||
option than to execute those modules. However: Execute isn't that critical (as
|
|
||||||
e.g. in pythoncomplete, which used to execute *every* import!), because it
|
|
||||||
means one import and no more. So basically the only dangerous thing is using
|
|
||||||
the import itself. If your ``c_builtin`` uses some strange initializations, it
|
|
||||||
might be dangerous. But if it does you're screwed anyways, because eventually
|
|
||||||
you're going to execute your code, which executes the import.
|
|
||||||
|
|
||||||
|
For :class:`.Script`
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Recipes
|
Security is an important topic for |jedi|. By default, no code is executed
|
||||||
-------
|
within Jedi. As long as you write pure Python, everything is inferred
|
||||||
|
statically. If you enable ``load_unsafe_extensions=True`` for your
|
||||||
|
:class:`.Project` and you use builtin modules (``c_builtin``) Jedi will execute
|
||||||
|
those modules. If you don't trust a code base, please do not enable that
|
||||||
|
option. It might lead to arbitrary code execution.
|
||||||
|
|
||||||
Here are some tips on how to use |jedi| efficiently.
|
For :class:`.Interpreter`
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want security for :class:`.Interpreter`, ``do not`` use it. Jedi does
|
||||||
.. _type-hinting:
|
execute properties and in general is not very careful to avoid code execution.
|
||||||
|
This is intentional: Most people trust the code bases they have imported,
|
||||||
Type Hinting
|
because at that point a malicious code base would have had code execution
|
||||||
~~~~~~~~~~~~
|
already.
|
||||||
|
|
||||||
If |jedi| cannot detect the type of a function argument correctly (due to the
|
|
||||||
dynamic nature of Python), you can help it by hinting the type using
|
|
||||||
one of the following docstring/annotation syntax styles:
|
|
||||||
|
|
||||||
**PEP-0484 style**
|
|
||||||
|
|
||||||
https://www.python.org/dev/peps/pep-0484/
|
|
||||||
|
|
||||||
function annotations
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
def myfunction(node: ProgramNode, foo: str) -> None:
|
|
||||||
"""Do something with a ``node``.
|
|
||||||
|
|
||||||
"""
|
|
||||||
node.| # complete here
|
|
||||||
|
|
||||||
|
|
||||||
assignment, for-loop and with-statement type hints (all Python versions).
|
|
||||||
Note that the type hints must be on the same line as the statement
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
x = foo() # type: int
|
|
||||||
x, y = 2, 3 # type: typing.Optional[int], typing.Union[int, str] # typing module is mostly supported
|
|
||||||
for key, value in foo.items(): # type: str, Employee # note that Employee must be in scope
|
|
||||||
pass
|
|
||||||
with foo() as f: # type: int
|
|
||||||
print(f + 3)
|
|
||||||
|
|
||||||
Most of the features in PEP-0484 are supported including the typing module
|
|
||||||
(for Python < 3.5 you have to do ``pip install typing`` to use these),
|
|
||||||
and forward references.
|
|
||||||
|
|
||||||
You can also use stub files.
|
|
||||||
|
|
||||||
|
|
||||||
**Sphinx style**
|
|
||||||
|
|
||||||
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
def myfunction(node, foo):
|
|
||||||
"""Do something with a ``node``.
|
|
||||||
|
|
||||||
:type node: ProgramNode
|
|
||||||
:param str foo: foo parameter description
|
|
||||||
|
|
||||||
"""
|
|
||||||
node.| # complete here
|
|
||||||
|
|
||||||
**Epydoc**
|
|
||||||
|
|
||||||
http://epydoc.sourceforge.net/manual-fields.html
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
def myfunction(node):
|
|
||||||
"""Do something with a ``node``.
|
|
||||||
|
|
||||||
@type node: ProgramNode
|
|
||||||
|
|
||||||
"""
|
|
||||||
node.| # complete here
|
|
||||||
|
|
||||||
**Numpydoc**
|
|
||||||
|
|
||||||
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
|
||||||
|
|
||||||
In order to support the numpydoc format, you need to install the `numpydoc
|
|
||||||
<https://pypi.python.org/pypi/numpydoc>`__ package.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
def foo(var1, var2, long_var_name='hi'):
|
|
||||||
r"""A one-line summary that does not use variable names or the
|
|
||||||
function name.
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
var1 : array_like
|
|
||||||
Array_like means all those objects -- lists, nested lists,
|
|
||||||
etc. -- that can be converted to an array. We can also
|
|
||||||
refer to variables like `var1`.
|
|
||||||
var2 : int
|
|
||||||
The type above can either refer to an actual Python type
|
|
||||||
(e.g. ``int``), or describe the type of the variable in more
|
|
||||||
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
|
||||||
long_variable_name : {'hi', 'ho'}, optional
|
|
||||||
Choices in brackets, default first when optional.
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
"""
|
|
||||||
var2.| # complete here
|
|
||||||
|
|
||||||
A little history
|
|
||||||
----------------
|
|
||||||
|
|
||||||
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
|
|
||||||
of the precognition the Jedi have. There's even an awesome `scene
|
|
||||||
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
|
|
||||||
|
|
||||||
But actually the name hasn't so much to do with Star Wars. It's part of my
|
|
||||||
second name.
|
|
||||||
|
|
||||||
After I explained Guido van Rossum, how some parts of my auto-completion work,
|
|
||||||
he said (we drank a beer or two):
|
|
||||||
|
|
||||||
*"Oh, that worries me..."*
|
|
||||||
|
|
||||||
When it's finished, I hope he'll like it :-)
|
|
||||||
|
|
||||||
I actually started Jedi, because there were no good solutions available for VIM.
|
|
||||||
Most auto-completions just didn't work well. The only good solution was PyCharm.
|
|
||||||
But I like my good old VIM. Rope was never really intended to be an
|
|
||||||
auto-completion (and also I really hate project folders for my Python scripts).
|
|
||||||
It's more of a refactoring suite. So I decided to do my own version of a
|
|
||||||
completion, which would execute non-dangerous code. But I soon realized, that
|
|
||||||
this wouldn't work. So I built an extremely recursive thing which understands
|
|
||||||
many of Python's key features.
|
|
||||||
|
|
||||||
By the way, I really tried to program it as understandable as possible. But I
|
|
||||||
think understanding it might need quite some time, because of its recursive
|
|
||||||
nature.
|
|
||||||
@@ -3,6 +3,15 @@
|
|||||||
Installation and Configuration
|
Installation and Configuration
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
.. warning:: Most people will want to install Jedi as a submodule/vendored and
|
||||||
|
not through pip/system wide. The reason for this is that it makes sense that
|
||||||
|
the plugin that uses Jedi has always access to it. Otherwise Jedi will not
|
||||||
|
work properly when virtualenvs are activated. So please read the
|
||||||
|
documentation of your editor/IDE plugin to install Jedi.
|
||||||
|
|
||||||
|
For plugin developers, Jedi works best if it is always available. Vendoring
|
||||||
|
is a pretty good option for that.
|
||||||
|
|
||||||
You can either include |jedi| as a submodule in your text editor plugin (like
|
You can either include |jedi| as a submodule in your text editor plugin (like
|
||||||
jedi-vim_ does by default), or you can install it systemwide.
|
jedi-vim_ does by default), or you can install it systemwide.
|
||||||
|
|
||||||
@@ -41,14 +50,6 @@ Arch Linux
|
|||||||
You can install |jedi| directly from official Arch Linux packages:
|
You can install |jedi| directly from official Arch Linux packages:
|
||||||
|
|
||||||
- `python-jedi <https://www.archlinux.org/packages/community/any/python-jedi/>`__
|
- `python-jedi <https://www.archlinux.org/packages/community/any/python-jedi/>`__
|
||||||
(Python 3)
|
|
||||||
- `python2-jedi <https://www.archlinux.org/packages/community/any/python2-jedi/>`__
|
|
||||||
(Python 2)
|
|
||||||
|
|
||||||
The specified Python version just refers to the *runtime environment* for
|
|
||||||
|jedi|. Use the Python 2 version if you're running vim (or whatever editor you
|
|
||||||
use) under Python 2. Otherwise, use the Python 3 version. But whatever version
|
|
||||||
you choose, both are able to complete both Python 2 and 3 *code*.
|
|
||||||
|
|
||||||
(There is also a packaged version of the vim plugin available:
|
(There is also a packaged version of the vim plugin available:
|
||||||
`vim-jedi at Arch Linux <https://www.archlinux.org/packages/community/any/vim-jedi/>`__.)
|
`vim-jedi at Arch Linux <https://www.archlinux.org/packages/community/any/vim-jedi/>`__.)
|
||||||
|
|||||||
+8
-12
@@ -3,21 +3,17 @@
|
|||||||
Jedi Testing
|
Jedi Testing
|
||||||
============
|
============
|
||||||
|
|
||||||
The test suite depends on ``tox`` and ``pytest``::
|
The test suite depends on ``pytest``::
|
||||||
|
|
||||||
pip install tox pytest
|
pip install pytest
|
||||||
|
|
||||||
To run the tests for all supported Python versions::
|
If you want to test only a specific Python version (e.g. Python 3.8), it is as
|
||||||
|
|
||||||
tox
|
|
||||||
|
|
||||||
If you want to test only a specific Python version (e.g. Python 2.7), it's as
|
|
||||||
easy as::
|
easy as::
|
||||||
|
|
||||||
tox -e py27
|
python3.8 -m pytest
|
||||||
|
|
||||||
Tests are also run automatically on `Travis CI
|
Tests are also run automatically on `GitHub Actions
|
||||||
<https://travis-ci.org/davidhalter/jedi/>`_.
|
<https://github.com/davidhalter/jedi/actions>`_.
|
||||||
|
|
||||||
You want to add a test for |jedi|? Great! We love that. Normally you should
|
You want to add a test for |jedi|? Great! We love that. Normally you should
|
||||||
write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would
|
write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would
|
||||||
@@ -28,8 +24,8 @@ simple and readable testing structure.
|
|||||||
|
|
||||||
.. _blackbox:
|
.. _blackbox:
|
||||||
|
|
||||||
Blackbox Tests (run.py)
|
Integration Tests (run.py)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. automodule:: test.run
|
.. automodule:: test.run
|
||||||
|
|
||||||
|
|||||||
+186
-28
@@ -1,79 +1,108 @@
|
|||||||
.. include:: ../global.rst
|
.. include:: ../global.rst
|
||||||
|
|
||||||
End User Usage
|
Using Jedi
|
||||||
==============
|
==========
|
||||||
|
|
||||||
If you are a not an IDE Developer, the odds are that you just want to use
|
|jedi| is can be used with a variety of :ref:`plugins <editor-plugins>`,
|
||||||
|jedi| as a browser plugin or in the shell. Yes that's :ref:`also possible
|
`language servers <language-servers>` and other software.
|
||||||
<repl-completion>`!
|
It is also possible to use |jedi| in the :ref:`Python shell or with IPython
|
||||||
|
<repl-completion>`.
|
||||||
|
|
||||||
|jedi| is relatively young and can be used in a variety of Plugins and
|
Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
|
||||||
Software. If your Editor/IDE is not among them, recommend |jedi| to your IDE
|
|
||||||
developers.
|
|
||||||
|
|
||||||
|
.. _language-servers:
|
||||||
|
|
||||||
|
Language Servers
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
|
||||||
|
- `python-language-server <https://github.com/palantir/python-language-server>`_
|
||||||
|
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
|
||||||
|
|
||||||
.. _editor-plugins:
|
.. _editor-plugins:
|
||||||
|
|
||||||
Editor Plugins
|
Editor Plugins
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Vim:
|
Vim
|
||||||
|
~~~
|
||||||
|
|
||||||
- jedi-vim_
|
- jedi-vim_
|
||||||
- YouCompleteMe_
|
- YouCompleteMe_
|
||||||
- deoplete-jedi_
|
- deoplete-jedi_
|
||||||
|
|
||||||
Emacs:
|
Visual Studio Code
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- `Python Extension`_
|
||||||
|
|
||||||
|
Emacs
|
||||||
|
~~~~~
|
||||||
|
|
||||||
- Jedi.el_
|
- Jedi.el_
|
||||||
- elpy_
|
- elpy_
|
||||||
- anaconda-mode_
|
- anaconda-mode_
|
||||||
|
|
||||||
Sublime Text 2/3:
|
Sublime Text 2/3
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- SublimeJEDI_ (ST2 & ST3)
|
- SublimeJEDI_ (ST2 & ST3)
|
||||||
- anaconda_ (only ST3)
|
- anaconda_ (only ST3)
|
||||||
|
|
||||||
SynWrite:
|
SynWrite
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
- SynJedi_
|
- SynJedi_
|
||||||
|
|
||||||
TextMate:
|
TextMate
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
- Textmate_ (Not sure if it's actually working)
|
- Textmate_ (Not sure if it's actually working)
|
||||||
|
|
||||||
Kate:
|
Kate
|
||||||
|
~~~~
|
||||||
|
|
||||||
- Kate_ version 4.13+ `supports it natively
|
- Kate_ version 4.13+ `supports it natively
|
||||||
<https://projects.kde.org/projects/kde/applications/kate/repository/entry/addons/kate/pate/src/plugins/python_autocomplete_jedi.py?rev=KDE%2F4.13>`__,
|
<https://projects.kde.org/projects/kde/applications/kate/repository/entry/addons/kate/pate/src/plugins/python_autocomplete_jedi.py?rev=KDE%2F4.13>`__,
|
||||||
you have to enable it, though.
|
you have to enable it, though.
|
||||||
|
|
||||||
Visual Studio Code:
|
Atom
|
||||||
|
~~~~
|
||||||
- `Python Extension`_
|
|
||||||
|
|
||||||
Atom:
|
|
||||||
|
|
||||||
- autocomplete-python-jedi_
|
- autocomplete-python-jedi_
|
||||||
|
|
||||||
GNOME Builder:
|
GNOME Builder
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
- `GNOME Builder`_ `supports it natively
|
- `GNOME Builder`_ `supports it natively
|
||||||
<https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__,
|
<https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__,
|
||||||
and is enabled by default.
|
and is enabled by default.
|
||||||
|
|
||||||
Gedit:
|
Gedit
|
||||||
|
~~~~~
|
||||||
|
|
||||||
- gedi_
|
- gedi_
|
||||||
|
|
||||||
Eric IDE:
|
Eric IDE
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
- `Eric IDE`_ (Available as a plugin)
|
- `Eric IDE`_ (Available as a plugin)
|
||||||
|
|
||||||
Web Debugger:
|
Web Debugger
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
- wdb_
|
- wdb_
|
||||||
|
|
||||||
|
xonsh shell
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Jedi is a preinstalled extension in `xonsh shell <https://xon.sh/contents.html>`_.
|
||||||
|
Run the following command to enable:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
xontrib load jedi
|
||||||
|
|
||||||
and many more!
|
and many more!
|
||||||
|
|
||||||
.. _repl-completion:
|
.. _repl-completion:
|
||||||
@@ -81,11 +110,14 @@ and many more!
|
|||||||
Tab Completion in the Python Shell
|
Tab Completion in the Python Shell
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
Starting with Ipython `6.0.0` Jedi is a dependency of IPython. Autocompletion
|
Jedi is a dependency of IPython. Autocompletion in IPython is therefore
|
||||||
in IPython is therefore possible without additional configuration.
|
possible without additional configuration.
|
||||||
|
|
||||||
|
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
|
||||||
|
can look like in a different shell.
|
||||||
|
|
||||||
There are two different options how you can use Jedi autocompletion in
|
There are two different options how you can use Jedi autocompletion in
|
||||||
your Python interpreter. One with your custom ``$HOME/.pythonrc.py`` file
|
your ``python`` interpreter. One with your custom ``$HOME/.pythonrc.py`` file
|
||||||
and one that uses ``PYTHONSTARTUP``.
|
and one that uses ``PYTHONSTARTUP``.
|
||||||
|
|
||||||
Using ``PYTHONSTARTUP``
|
Using ``PYTHONSTARTUP``
|
||||||
@@ -93,11 +125,137 @@ Using ``PYTHONSTARTUP``
|
|||||||
|
|
||||||
.. automodule:: jedi.api.replstartup
|
.. automodule:: jedi.api.replstartup
|
||||||
|
|
||||||
Using a custom ``$HOME/.pythonrc.py``
|
Using a Custom ``$HOME/.pythonrc.py``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autofunction:: jedi.utils.setup_readline
|
.. autofunction:: jedi.utils.setup_readline
|
||||||
|
|
||||||
|
.. _recipes:
|
||||||
|
|
||||||
|
Recipes
|
||||||
|
-------
|
||||||
|
|
||||||
|
Here are some tips on how to use |jedi| efficiently.
|
||||||
|
|
||||||
|
|
||||||
|
.. _type-hinting:
|
||||||
|
|
||||||
|
Type Hinting
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If |jedi| cannot detect the type of a function argument correctly (due to the
|
||||||
|
dynamic nature of Python), you can help it by hinting the type using
|
||||||
|
one of the docstring/annotation styles below. **Only gradual typing will
|
||||||
|
always work**, all the docstring solutions are glorified hacks and more
|
||||||
|
complicated cases will probably not work.
|
||||||
|
|
||||||
|
Official Gradual Typing (Recommended)
|
||||||
|
+++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
You can read a lot about Python's gradual typing system in the corresponding
|
||||||
|
PEPs like:
|
||||||
|
|
||||||
|
- `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ as an introduction
|
||||||
|
- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ for variable annotations
|
||||||
|
- `PEP 589 <https://www.python.org/dev/peps/pep-0589/>`_ for ``TypeDict``
|
||||||
|
- There are probably more :)
|
||||||
|
|
||||||
|
Below you can find a few examples how you can use this feature.
|
||||||
|
|
||||||
|
Function annotations::
|
||||||
|
|
||||||
|
def myfunction(node: ProgramNode, foo: str) -> None:
|
||||||
|
"""Do something with a ``node``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
node.| # complete here
|
||||||
|
|
||||||
|
|
||||||
|
Assignment, for-loop and with-statement type hints::
|
||||||
|
|
||||||
|
import typing
|
||||||
|
x: int = foo()
|
||||||
|
y: typing.Optional[int] = 3
|
||||||
|
|
||||||
|
key: str
|
||||||
|
value: Employee
|
||||||
|
for key, value in foo.items():
|
||||||
|
pass
|
||||||
|
|
||||||
|
f: Union[int, float]
|
||||||
|
with foo() as f:
|
||||||
|
print(f + 3)
|
||||||
|
|
||||||
|
PEP-0484 should be supported in its entirety. Feel free to open issues if that
|
||||||
|
is not the case. You can also use stub files.
|
||||||
|
|
||||||
|
|
||||||
|
Sphinx style
|
||||||
|
++++++++++++
|
||||||
|
|
||||||
|
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
def myfunction(node, foo):
|
||||||
|
"""
|
||||||
|
Do something with a ``node``.
|
||||||
|
|
||||||
|
:type node: ProgramNode
|
||||||
|
:param str foo: foo parameter description
|
||||||
|
"""
|
||||||
|
node.| # complete here
|
||||||
|
|
||||||
|
Epydoc
|
||||||
|
++++++
|
||||||
|
|
||||||
|
http://epydoc.sourceforge.net/manual-fields.html
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
def myfunction(node):
|
||||||
|
"""
|
||||||
|
Do something with a ``node``.
|
||||||
|
|
||||||
|
@type node: ProgramNode
|
||||||
|
"""
|
||||||
|
node.| # complete here
|
||||||
|
|
||||||
|
Numpydoc
|
||||||
|
++++++++
|
||||||
|
|
||||||
|
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
||||||
|
|
||||||
|
In order to support the numpydoc format, you need to install the `numpydoc
|
||||||
|
<https://pypi.python.org/pypi/numpydoc>`__ package.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
def foo(var1, var2, long_var_name='hi'):
|
||||||
|
r"""
|
||||||
|
A one-line summary that does not use variable names or the
|
||||||
|
function name.
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
var1 : array_like
|
||||||
|
Array_like means all those objects -- lists, nested lists,
|
||||||
|
etc. -- that can be converted to an array. We can also
|
||||||
|
refer to variables like `var1`.
|
||||||
|
var2 : int
|
||||||
|
The type above can either refer to an actual Python type
|
||||||
|
(e.g. ``int``), or describe the type of the variable in more
|
||||||
|
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
||||||
|
long_variable_name : {'hi', 'ho'}, optional
|
||||||
|
Choices in brackets, default first when optional.
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
"""
|
||||||
|
var2.| # complete here
|
||||||
|
|
||||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||||
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
|
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
|
||||||
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
||||||
@@ -114,4 +272,4 @@ Using a custom ``$HOME/.pythonrc.py``
|
|||||||
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/
|
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/
|
||||||
.. _gedi: https://github.com/isamert/gedi
|
.. _gedi: https://github.com/isamert/gedi
|
||||||
.. _Eric IDE: https://eric-ide.python-projects.org
|
.. _Eric IDE: https://eric-ide.python-projects.org
|
||||||
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python
|
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=ms-python.python
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
:orphan:
|
:orphan:
|
||||||
|
|
||||||
.. |jedi| replace:: *Jedi*
|
.. |jedi| replace:: Jedi
|
||||||
+39
-7
@@ -1,13 +1,40 @@
|
|||||||
.. include global.rst
|
.. include global.rst
|
||||||
|
|
||||||
Jedi - an awesome autocompletion/static analysis library for Python
|
.. meta::
|
||||||
===================================================================
|
:github_url: https://github.com/davidhalter/jedi
|
||||||
|
|
||||||
Release v\ |release|. (:doc:`Installation <docs/installation>`)
|
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
|
||||||
|
====================================================================================
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/github/stars/davidhalter/jedi.svg?style=social&label=Star&maxAge=2592000
|
||||||
|
:target: https://github.com/davidhalter/jedi
|
||||||
|
:alt: GitHub stars
|
||||||
|
|
||||||
|
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
|
||||||
|
:target: https://github.com/davidhalter/jedi/issues
|
||||||
|
:alt: The percentage of open issues and pull requests
|
||||||
|
|
||||||
|
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
|
||||||
|
:target: https://github.com/davidhalter/jedi/issues
|
||||||
|
:alt: The resolution time is the median time an issue or pull request stays open.
|
||||||
|
|
||||||
|
.. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
|
||||||
|
:target: https://github.com/davidhalter/jedi/actions
|
||||||
|
:alt: Tests
|
||||||
|
|
||||||
|
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
|
||||||
|
:target: https://coveralls.io/r/davidhalter/jedi
|
||||||
|
:alt: Coverage status
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/jedi
|
||||||
|
:target: https://pepy.tech/project/jedi
|
||||||
|
:alt: PyPI Downloads
|
||||||
|
|
||||||
|
`Github Repository <https://github.com/davidhalter/jedi>`_
|
||||||
|
|
||||||
.. automodule:: jedi
|
.. automodule:: jedi
|
||||||
|
|
||||||
Autocompletion can look like this (e.g. VIM plugin):
|
Autocompletion can for example look like this in jedi-vim:
|
||||||
|
|
||||||
.. figure:: _screenshots/screenshot_complete.png
|
.. figure:: _screenshots/screenshot_complete.png
|
||||||
|
|
||||||
@@ -18,16 +45,18 @@ Docs
|
|||||||
----
|
----
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 1
|
||||||
|
|
||||||
docs/usage
|
docs/usage
|
||||||
docs/installation
|
|
||||||
docs/features
|
docs/features
|
||||||
docs/api
|
docs/api
|
||||||
docs/api-classes
|
docs/api-classes
|
||||||
|
docs/installation
|
||||||
docs/settings
|
docs/settings
|
||||||
docs/development
|
docs/development
|
||||||
docs/testing
|
docs/testing
|
||||||
|
docs/acknowledgements
|
||||||
|
docs/changelog
|
||||||
|
|
||||||
|
|
||||||
.. _resources:
|
.. _resources:
|
||||||
@@ -35,6 +64,9 @@ Docs
|
|||||||
Resources
|
Resources
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
If you want to stay **up-to-date** with releases, please **subscribe** to this
|
||||||
|
mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
|
||||||
|
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
|
||||||
|
|
||||||
- `Source Code on Github <https://github.com/davidhalter/jedi>`_
|
- `Source Code on Github <https://github.com/davidhalter/jedi>`_
|
||||||
- `Travis Testing <https://travis-ci.org/davidhalter/jedi>`_
|
|
||||||
- `Python Package Index <https://pypi.python.org/pypi/jedi/>`_
|
- `Python Package Index <https://pypi.python.org/pypi/jedi/>`_
|
||||||
+14
-19
@@ -1,16 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
Jedi is a static analysis tool for Python that can be used in IDEs/editors.
|
Jedi is a static analysis tool for Python that is typically used in
|
||||||
Jedi has a focus on autocompletion and goto functionality. Jedi is fast and is
|
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
|
||||||
very well tested. It understands Python and stubs on a deep level.
|
functionality. Other features include refactoring, code search and finding
|
||||||
|
references.
|
||||||
|
|
||||||
Jedi has support for different goto functions. It's possible to search for
|
Jedi has a simple API to work with. There is a reference implementation as a
|
||||||
references and list names in a Python file to get information about them.
|
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
|
||||||
|
REPL is also possible, IPython uses it natively and for the CPython REPL you
|
||||||
Jedi uses a very simple API to connect with IDE's. There's a reference
|
can install it. Jedi is well tested and bugs should be rare.
|
||||||
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
|
|
||||||
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
|
|
||||||
Autocompletion in your REPL is also possible, IPython uses it natively and for
|
|
||||||
the CPython REPL you have to install it.
|
|
||||||
|
|
||||||
Here's a simple example of the autocompletion feature:
|
Here's a simple example of the autocompletion feature:
|
||||||
|
|
||||||
@@ -28,20 +25,18 @@ Here's a simple example of the autocompletion feature:
|
|||||||
ad
|
ad
|
||||||
>>> print(completions[0].name)
|
>>> print(completions[0].name)
|
||||||
load
|
load
|
||||||
|
|
||||||
As you see Jedi is pretty simple and allows you to concentrate on writing a
|
|
||||||
good text editor, while still having very good IDE features for Python.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.16.0'
|
__version__ = '0.18.0'
|
||||||
|
|
||||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
from jedi.api import Script, Interpreter, set_debug_function, preload_module
|
||||||
preload_module, names
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.api.environment import find_virtualenvs, find_system_environments, \
|
from jedi.api.environment import find_virtualenvs, find_system_environments, \
|
||||||
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
||||||
get_system_environment
|
get_system_environment, InterpreterEnvironment
|
||||||
from jedi.api.exceptions import InternalError
|
from jedi.api.project import Project, get_default_project
|
||||||
|
from jedi.api.exceptions import InternalError, RefactoringError
|
||||||
|
|
||||||
# Finally load the internal plugins. This is only internal.
|
# Finally load the internal plugins. This is only internal.
|
||||||
from jedi.plugins import registry
|
from jedi.plugins import registry
|
||||||
del registry
|
del registry
|
||||||
+15
-5
@@ -27,8 +27,8 @@ def _start_linter():
|
|||||||
paths = [path]
|
paths = [path]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for path in paths:
|
for p in paths:
|
||||||
for error in jedi.Script(path=path)._analysis():
|
for error in jedi.Script(path=p)._analysis():
|
||||||
print(error)
|
print(error)
|
||||||
except Exception:
|
except Exception:
|
||||||
if '--pdb' in sys.argv:
|
if '--pdb' in sys.argv:
|
||||||
@@ -44,19 +44,29 @@ def _complete():
|
|||||||
import jedi
|
import jedi
|
||||||
import pdb
|
import pdb
|
||||||
|
|
||||||
|
if '-d' in sys.argv:
|
||||||
|
sys.argv.remove('-d')
|
||||||
|
jedi.set_debug_function()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for c in jedi.Script(sys.argv[2]).complete():
|
completions = jedi.Script(sys.argv[2]).complete()
|
||||||
|
for c in completions:
|
||||||
c.docstring()
|
c.docstring()
|
||||||
c.type
|
c.type
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
print(repr(e))
|
||||||
pdb.post_mortem()
|
pdb.post_mortem()
|
||||||
|
else:
|
||||||
|
print(completions)
|
||||||
|
|
||||||
|
|
||||||
if len(sys.argv) == 2 and sys.argv[1] == 'repl':
|
if len(sys.argv) == 2 and sys.argv[1] == 'repl':
|
||||||
# don't want to use __main__ only for repl yet, maybe we want to use it for
|
# don't want to use __main__ only for repl yet, maybe we want to use it for
|
||||||
# something else. So just use the keyword ``repl`` for now.
|
# something else. So just use the keyword ``repl`` for now.
|
||||||
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
|
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
|
||||||
elif len(sys.argv) > 1 and sys.argv[1] == 'linter':
|
elif len(sys.argv) > 1 and sys.argv[1] == '_linter':
|
||||||
_start_linter()
|
_start_linter()
|
||||||
elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
|
elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
|
||||||
_complete()
|
_complete()
|
||||||
|
else:
|
||||||
|
print('Command not implemented: %s' % sys.argv[1])
|
||||||
+7
-693
@@ -1,344 +1,13 @@
|
|||||||
"""
|
"""
|
||||||
To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been
|
This module is here to ensure compatibility of Windows/Linux/MacOS and
|
||||||
created. Clearly there is huge need to use conforming syntax.
|
different Python versions.
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
import atexit
|
|
||||||
import errno
|
import errno
|
||||||
import functools
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import pickle
|
||||||
import re
|
|
||||||
import pkgutil
|
|
||||||
import warnings
|
|
||||||
import inspect
|
|
||||||
import subprocess
|
|
||||||
import weakref
|
|
||||||
try:
|
|
||||||
import importlib
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
from zipimport import zipimporter
|
|
||||||
|
|
||||||
from jedi.file_io import KnownContentFileIO, ZipFileIO
|
|
||||||
|
|
||||||
is_py3 = sys.version_info[0] >= 3
|
def cast_path(string):
|
||||||
is_py35 = is_py3 and sys.version_info[1] >= 5
|
|
||||||
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
|
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 5):
|
|
||||||
"""
|
|
||||||
A super-minimal shim around listdir that behave like
|
|
||||||
scandir for the information we need.
|
|
||||||
"""
|
|
||||||
class _DirEntry:
|
|
||||||
|
|
||||||
def __init__(self, name, basepath):
|
|
||||||
self.name = name
|
|
||||||
self.basepath = basepath
|
|
||||||
|
|
||||||
def is_dir(self):
|
|
||||||
path_for_name = os.path.join(self.basepath, self.name)
|
|
||||||
return os.path.isdir(path_for_name)
|
|
||||||
|
|
||||||
def scandir(dir):
|
|
||||||
return [_DirEntry(name, dir) for name in os.listdir(dir)]
|
|
||||||
else:
|
|
||||||
from os import scandir
|
|
||||||
|
|
||||||
|
|
||||||
class DummyFile(object):
|
|
||||||
def __init__(self, loader, string):
|
|
||||||
self.loader = loader
|
|
||||||
self.string = string
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
return self.loader.get_source(self.string)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
del self.loader
|
|
||||||
|
|
||||||
|
|
||||||
def find_module_py34(string, path=None, full_name=None, is_global_search=True):
|
|
||||||
spec = None
|
|
||||||
loader = None
|
|
||||||
|
|
||||||
for finder in sys.meta_path:
|
|
||||||
if is_global_search and finder != importlib.machinery.PathFinder:
|
|
||||||
p = None
|
|
||||||
else:
|
|
||||||
p = path
|
|
||||||
try:
|
|
||||||
find_spec = finder.find_spec
|
|
||||||
except AttributeError:
|
|
||||||
# These are old-school clases that still have a different API, just
|
|
||||||
# ignore those.
|
|
||||||
continue
|
|
||||||
|
|
||||||
spec = find_spec(string, p)
|
|
||||||
if spec is not None:
|
|
||||||
loader = spec.loader
|
|
||||||
if loader is None and not spec.has_location:
|
|
||||||
# This is a namespace package.
|
|
||||||
full_name = string if not path else full_name
|
|
||||||
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
|
|
||||||
return implicit_ns_info, True
|
|
||||||
break
|
|
||||||
|
|
||||||
return find_module_py33(string, path, loader)
|
|
||||||
|
|
||||||
|
|
||||||
def find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
|
|
||||||
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
|
|
||||||
|
|
||||||
if loader is None and path is None: # Fallback to find builtins
|
|
||||||
try:
|
|
||||||
with warnings.catch_warnings(record=True):
|
|
||||||
# Mute "DeprecationWarning: Use importlib.util.find_spec()
|
|
||||||
# instead." While we should replace that in the future, it's
|
|
||||||
# probably good to wait until we deprecate Python 3.3, since
|
|
||||||
# it was added in Python 3.4 and find_loader hasn't been
|
|
||||||
# removed in 3.6.
|
|
||||||
loader = importlib.find_loader(string)
|
|
||||||
except ValueError as e:
|
|
||||||
# See #491. Importlib might raise a ValueError, to avoid this, we
|
|
||||||
# just raise an ImportError to fix the issue.
|
|
||||||
raise ImportError("Originally " + repr(e))
|
|
||||||
|
|
||||||
if loader is None:
|
|
||||||
raise ImportError("Couldn't find a loader for {}".format(string))
|
|
||||||
|
|
||||||
return _from_loader(loader, string)
|
|
||||||
|
|
||||||
|
|
||||||
def _from_loader(loader, string):
|
|
||||||
is_package = loader.is_package(string)
|
|
||||||
try:
|
|
||||||
get_filename = loader.get_filename
|
|
||||||
except AttributeError:
|
|
||||||
return None, is_package
|
|
||||||
else:
|
|
||||||
module_path = cast_path(get_filename(string))
|
|
||||||
|
|
||||||
# To avoid unicode and read bytes, "overwrite" loader.get_source if
|
|
||||||
# possible.
|
|
||||||
f = type(loader).get_source
|
|
||||||
if is_py3 and f is not importlib.machinery.SourceFileLoader.get_source:
|
|
||||||
# Unfortunately we are reading unicode here, not bytes.
|
|
||||||
# It seems hard to get bytes, because the zip importer
|
|
||||||
# logic just unpacks the zip file and returns a file descriptor
|
|
||||||
# that we cannot as easily access. Therefore we just read it as
|
|
||||||
# a string in the cases where get_source was overwritten.
|
|
||||||
code = loader.get_source(string)
|
|
||||||
else:
|
|
||||||
code = _get_source(loader, string)
|
|
||||||
|
|
||||||
if code is None:
|
|
||||||
return None, is_package
|
|
||||||
if isinstance(loader, zipimporter):
|
|
||||||
return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package
|
|
||||||
|
|
||||||
return KnownContentFileIO(module_path, code), is_package
|
|
||||||
|
|
||||||
|
|
||||||
def _get_source(loader, fullname):
|
|
||||||
"""
|
|
||||||
This method is here as a replacement for SourceLoader.get_source. That
|
|
||||||
method returns unicode, but we prefer bytes.
|
|
||||||
"""
|
|
||||||
path = loader.get_filename(fullname)
|
|
||||||
try:
|
|
||||||
return loader.get_data(path)
|
|
||||||
except OSError:
|
|
||||||
raise ImportError('source not available through get_data()',
|
|
||||||
name=fullname)
|
|
||||||
|
|
||||||
|
|
||||||
def find_module_pre_py3(string, path=None, full_name=None, is_global_search=True):
|
|
||||||
# This import is here, because in other places it will raise a
|
|
||||||
# DeprecationWarning.
|
|
||||||
import imp
|
|
||||||
try:
|
|
||||||
module_file, module_path, description = imp.find_module(string, path)
|
|
||||||
module_type = description[2]
|
|
||||||
is_package = module_type is imp.PKG_DIRECTORY
|
|
||||||
if is_package:
|
|
||||||
# In Python 2 directory package imports are returned as folder
|
|
||||||
# paths, not __init__.py paths.
|
|
||||||
p = os.path.join(module_path, '__init__.py')
|
|
||||||
try:
|
|
||||||
module_file = open(p)
|
|
||||||
module_path = p
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
elif module_type != imp.PY_SOURCE:
|
|
||||||
if module_file is not None:
|
|
||||||
module_file.close()
|
|
||||||
module_file = None
|
|
||||||
|
|
||||||
if module_file is None:
|
|
||||||
code = None
|
|
||||||
return None, is_package
|
|
||||||
|
|
||||||
with module_file:
|
|
||||||
code = module_file.read()
|
|
||||||
return KnownContentFileIO(cast_path(module_path), code), is_package
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if path is None:
|
|
||||||
path = sys.path
|
|
||||||
for item in path:
|
|
||||||
loader = pkgutil.get_importer(item)
|
|
||||||
if loader:
|
|
||||||
loader = loader.find_module(string)
|
|
||||||
if loader is not None:
|
|
||||||
return _from_loader(loader, string)
|
|
||||||
raise ImportError("No module named {}".format(string))
|
|
||||||
|
|
||||||
|
|
||||||
find_module = find_module_py34 if is_py3 else find_module_pre_py3
|
|
||||||
find_module.__doc__ = """
|
|
||||||
Provides information about a module.
|
|
||||||
|
|
||||||
This function isolates the differences in importing libraries introduced with
|
|
||||||
python 3.3 on; it gets a module name and optionally a path. It will return a
|
|
||||||
tuple containin an open file for the module (if not builtin), the filename
|
|
||||||
or the name of the module if it is a builtin one and a boolean indicating
|
|
||||||
if the module is contained in a package.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _iter_modules(paths, prefix=''):
|
|
||||||
# Copy of pkgutil.iter_modules adapted to work with namespaces
|
|
||||||
|
|
||||||
for path in paths:
|
|
||||||
importer = pkgutil.get_importer(path)
|
|
||||||
|
|
||||||
if not isinstance(importer, importlib.machinery.FileFinder):
|
|
||||||
# We're only modifying the case for FileFinder. All the other cases
|
|
||||||
# still need to be checked (like zip-importing). Do this by just
|
|
||||||
# calling the pkgutil version.
|
|
||||||
for mod_info in pkgutil.iter_modules([path], prefix):
|
|
||||||
yield mod_info
|
|
||||||
continue
|
|
||||||
|
|
||||||
# START COPY OF pkutils._iter_file_finder_modules.
|
|
||||||
if importer.path is None or not os.path.isdir(importer.path):
|
|
||||||
return
|
|
||||||
|
|
||||||
yielded = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
filenames = os.listdir(importer.path)
|
|
||||||
except OSError:
|
|
||||||
# ignore unreadable directories like import does
|
|
||||||
filenames = []
|
|
||||||
filenames.sort() # handle packages before same-named modules
|
|
||||||
|
|
||||||
for fn in filenames:
|
|
||||||
modname = inspect.getmodulename(fn)
|
|
||||||
if modname == '__init__' or modname in yielded:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# jedi addition: Avoid traversing special directories
|
|
||||||
if fn.startswith('.') or fn == '__pycache__':
|
|
||||||
continue
|
|
||||||
|
|
||||||
path = os.path.join(importer.path, fn)
|
|
||||||
ispkg = False
|
|
||||||
|
|
||||||
if not modname and os.path.isdir(path) and '.' not in fn:
|
|
||||||
modname = fn
|
|
||||||
# A few jedi modifications: Don't check if there's an
|
|
||||||
# __init__.py
|
|
||||||
try:
|
|
||||||
os.listdir(path)
|
|
||||||
except OSError:
|
|
||||||
# ignore unreadable directories like import does
|
|
||||||
continue
|
|
||||||
ispkg = True
|
|
||||||
|
|
||||||
if modname and '.' not in modname:
|
|
||||||
yielded[modname] = 1
|
|
||||||
yield importer, prefix + modname, ispkg
|
|
||||||
# END COPY
|
|
||||||
|
|
||||||
|
|
||||||
iter_modules = _iter_modules if py_version >= 34 else pkgutil.iter_modules
|
|
||||||
|
|
||||||
|
|
||||||
class ImplicitNSInfo(object):
|
|
||||||
"""Stores information returned from an implicit namespace spec"""
|
|
||||||
def __init__(self, name, paths):
|
|
||||||
self.name = name
|
|
||||||
self.paths = paths
|
|
||||||
|
|
||||||
|
|
||||||
if is_py3:
|
|
||||||
all_suffixes = importlib.machinery.all_suffixes
|
|
||||||
else:
|
|
||||||
def all_suffixes():
|
|
||||||
# Is deprecated and raises a warning in Python 3.6.
|
|
||||||
import imp
|
|
||||||
return [suffix for suffix, _, _ in imp.get_suffixes()]
|
|
||||||
|
|
||||||
|
|
||||||
# unicode function
|
|
||||||
try:
|
|
||||||
unicode = unicode
|
|
||||||
except NameError:
|
|
||||||
unicode = str
|
|
||||||
|
|
||||||
|
|
||||||
# re-raise function
|
|
||||||
if is_py3:
|
|
||||||
def reraise(exception, traceback):
|
|
||||||
raise exception.with_traceback(traceback)
|
|
||||||
else:
|
|
||||||
eval(compile("""
|
|
||||||
def reraise(exception, traceback):
|
|
||||||
raise exception, None, traceback
|
|
||||||
""", 'blub', 'exec'))
|
|
||||||
|
|
||||||
reraise.__doc__ = """
|
|
||||||
Re-raise `exception` with a `traceback` object.
|
|
||||||
|
|
||||||
Usage::
|
|
||||||
|
|
||||||
reraise(Exception, sys.exc_info()[2])
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def use_metaclass(meta, *bases):
|
|
||||||
""" Create a class with a metaclass. """
|
|
||||||
if not bases:
|
|
||||||
bases = (object,)
|
|
||||||
return meta("Py2CompatibilityMetaClass", bases, {})
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
encoding = sys.stdout.encoding
|
|
||||||
if encoding is None:
|
|
||||||
encoding = 'utf-8'
|
|
||||||
except AttributeError:
|
|
||||||
encoding = 'ascii'
|
|
||||||
|
|
||||||
|
|
||||||
def u(string, errors='strict'):
|
|
||||||
"""Cast to unicode DAMMIT!
|
|
||||||
Written because Python2 repr always implicitly casts to a string, so we
|
|
||||||
have to cast back to a unicode (and we now that we always deal with valid
|
|
||||||
unicode, because we check that in the beginning).
|
|
||||||
"""
|
|
||||||
if isinstance(string, bytes):
|
|
||||||
return unicode(string, encoding='UTF-8', errors=errors)
|
|
||||||
return string
|
|
||||||
|
|
||||||
|
|
||||||
def cast_path(obj):
|
|
||||||
"""
|
"""
|
||||||
Take a bytes or str path and cast it to unicode.
|
Take a bytes or str path and cast it to unicode.
|
||||||
|
|
||||||
@@ -349,154 +18,13 @@ def cast_path(obj):
|
|||||||
Since this just really complicates everything and Python 2.7 will be EOL
|
Since this just really complicates everything and Python 2.7 will be EOL
|
||||||
soon anyway, just go with always strings.
|
soon anyway, just go with always strings.
|
||||||
"""
|
"""
|
||||||
return u(obj, errors='replace')
|
if isinstance(string, bytes):
|
||||||
|
return str(string, encoding='UTF-8', errors='replace')
|
||||||
|
return str(string)
|
||||||
def force_unicode(obj):
|
|
||||||
# Intentionally don't mix those two up, because those two code paths might
|
|
||||||
# be different in the future (maybe windows?).
|
|
||||||
return cast_path(obj)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import builtins # module name in python 3
|
|
||||||
except ImportError:
|
|
||||||
import __builtin__ as builtins # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
import ast # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
def literal_eval(string):
|
|
||||||
return ast.literal_eval(string)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from itertools import zip_longest
|
|
||||||
except ImportError:
|
|
||||||
from itertools import izip_longest as zip_longest # Python 2 # noqa: F401
|
|
||||||
|
|
||||||
try:
|
|
||||||
FileNotFoundError = FileNotFoundError
|
|
||||||
except NameError:
|
|
||||||
FileNotFoundError = IOError
|
|
||||||
|
|
||||||
try:
|
|
||||||
IsADirectoryError = IsADirectoryError
|
|
||||||
except NameError:
|
|
||||||
IsADirectoryError = IOError
|
|
||||||
|
|
||||||
try:
|
|
||||||
PermissionError = PermissionError
|
|
||||||
except NameError:
|
|
||||||
PermissionError = IOError
|
|
||||||
|
|
||||||
|
|
||||||
def no_unicode_pprint(dct):
|
|
||||||
"""
|
|
||||||
Python 2/3 dict __repr__ may be different, because of unicode differens
|
|
||||||
(with or without a `u` prefix). Normally in doctests we could use `pprint`
|
|
||||||
to sort dicts and check for equality, but here we have to write a separate
|
|
||||||
function to do that.
|
|
||||||
"""
|
|
||||||
import pprint
|
|
||||||
s = pprint.pformat(dct)
|
|
||||||
print(re.sub("u'", "'", s))
|
|
||||||
|
|
||||||
|
|
||||||
def utf8_repr(func):
|
|
||||||
"""
|
|
||||||
``__repr__`` methods in Python 2 don't allow unicode objects to be
|
|
||||||
returned. Therefore cast them to utf-8 bytes in this decorator.
|
|
||||||
"""
|
|
||||||
def wrapper(self):
|
|
||||||
result = func(self)
|
|
||||||
if isinstance(result, unicode):
|
|
||||||
return result.encode('utf-8')
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
if is_py3:
|
|
||||||
return func
|
|
||||||
else:
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
if is_py3:
|
|
||||||
import queue
|
|
||||||
else:
|
|
||||||
import Queue as queue # noqa: F401
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Attempt to load the C implementation of pickle on Python 2 as it is way
|
|
||||||
# faster.
|
|
||||||
import cPickle as pickle
|
|
||||||
except ImportError:
|
|
||||||
import pickle
|
|
||||||
if sys.version_info[:2] == (3, 3):
|
|
||||||
"""
|
|
||||||
Monkeypatch the unpickler in Python 3.3. This is needed, because the
|
|
||||||
argument `encoding='bytes'` is not supported in 3.3, but badly needed to
|
|
||||||
communicate with Python 2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class NewUnpickler(pickle._Unpickler):
|
|
||||||
dispatch = dict(pickle._Unpickler.dispatch)
|
|
||||||
|
|
||||||
def _decode_string(self, value):
|
|
||||||
# Used to allow strings from Python 2 to be decoded either as
|
|
||||||
# bytes or Unicode strings. This should be used only with the
|
|
||||||
# STRING, BINSTRING and SHORT_BINSTRING opcodes.
|
|
||||||
if self.encoding == "bytes":
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return value.decode(self.encoding, self.errors)
|
|
||||||
|
|
||||||
def load_string(self):
|
|
||||||
data = self.readline()[:-1]
|
|
||||||
# Strip outermost quotes
|
|
||||||
if len(data) >= 2 and data[0] == data[-1] and data[0] in b'"\'':
|
|
||||||
data = data[1:-1]
|
|
||||||
else:
|
|
||||||
raise pickle.UnpicklingError("the STRING opcode argument must be quoted")
|
|
||||||
self.append(self._decode_string(pickle.codecs.escape_decode(data)[0]))
|
|
||||||
dispatch[pickle.STRING[0]] = load_string
|
|
||||||
|
|
||||||
def load_binstring(self):
|
|
||||||
# Deprecated BINSTRING uses signed 32-bit length
|
|
||||||
len, = pickle.struct.unpack('<i', self.read(4))
|
|
||||||
if len < 0:
|
|
||||||
raise pickle.UnpicklingError("BINSTRING pickle has negative byte count")
|
|
||||||
data = self.read(len)
|
|
||||||
self.append(self._decode_string(data))
|
|
||||||
dispatch[pickle.BINSTRING[0]] = load_binstring
|
|
||||||
|
|
||||||
def load_short_binstring(self):
|
|
||||||
len = self.read(1)[0]
|
|
||||||
data = self.read(len)
|
|
||||||
self.append(self._decode_string(data))
|
|
||||||
dispatch[pickle.SHORT_BINSTRING[0]] = load_short_binstring
|
|
||||||
|
|
||||||
def load(file, fix_imports=True, encoding="ASCII", errors="strict"):
|
|
||||||
return NewUnpickler(file, fix_imports=fix_imports,
|
|
||||||
encoding=encoding, errors=errors).load()
|
|
||||||
|
|
||||||
def loads(s, fix_imports=True, encoding="ASCII", errors="strict"):
|
|
||||||
if isinstance(s, str):
|
|
||||||
raise TypeError("Can't load pickle from unicode string")
|
|
||||||
file = pickle.io.BytesIO(s)
|
|
||||||
return NewUnpickler(file, fix_imports=fix_imports,
|
|
||||||
encoding=encoding, errors=errors).load()
|
|
||||||
|
|
||||||
pickle.Unpickler = NewUnpickler
|
|
||||||
pickle.load = load
|
|
||||||
pickle.loads = loads
|
|
||||||
|
|
||||||
|
|
||||||
def pickle_load(file):
|
def pickle_load(file):
|
||||||
try:
|
try:
|
||||||
if is_py3:
|
|
||||||
return pickle.load(file, encoding='bytes')
|
|
||||||
return pickle.load(file)
|
return pickle.load(file)
|
||||||
# Python on Windows don't throw EOF errors for pipes. So reraise them with
|
# Python on Windows don't throw EOF errors for pipes. So reraise them with
|
||||||
# the correct type, which is caught upwards.
|
# the correct type, which is caught upwards.
|
||||||
@@ -506,24 +34,8 @@ def pickle_load(file):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _python2_dct_keys_to_unicode(data):
|
|
||||||
"""
|
|
||||||
Python 2 stores object __dict__ entries as bytes, not unicode, correct it
|
|
||||||
here. Python 2 can deal with both, Python 3 expects unicode.
|
|
||||||
"""
|
|
||||||
if isinstance(data, tuple):
|
|
||||||
return tuple(_python2_dct_keys_to_unicode(x) for x in data)
|
|
||||||
elif isinstance(data, list):
|
|
||||||
return list(_python2_dct_keys_to_unicode(x) for x in data)
|
|
||||||
elif hasattr(data, '__dict__') and type(data.__dict__) == dict:
|
|
||||||
data.__dict__ = {unicode(k): v for k, v in data.__dict__.items()}
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def pickle_dump(data, file, protocol):
|
def pickle_dump(data, file, protocol):
|
||||||
try:
|
try:
|
||||||
if not is_py3:
|
|
||||||
data = _python2_dct_keys_to_unicode(data)
|
|
||||||
pickle.dump(data, file, protocol)
|
pickle.dump(data, file, protocol)
|
||||||
# On Python 3.3 flush throws sometimes an error even though the writing
|
# On Python 3.3 flush throws sometimes an error even though the writing
|
||||||
# operation should be completed.
|
# operation should be completed.
|
||||||
@@ -534,201 +46,3 @@ def pickle_dump(data, file, protocol):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
raise IOError(errno.EPIPE, "Broken pipe")
|
raise IOError(errno.EPIPE, "Broken pipe")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
# Determine the highest protocol version compatible for a given list of Python
|
|
||||||
# versions.
|
|
||||||
def highest_pickle_protocol(python_versions):
|
|
||||||
protocol = 4
|
|
||||||
for version in python_versions:
|
|
||||||
if version[0] == 2:
|
|
||||||
# The minimum protocol version for the versions of Python that we
|
|
||||||
# support (2.7 and 3.3+) is 2.
|
|
||||||
return 2
|
|
||||||
if version[1] < 4:
|
|
||||||
protocol = 3
|
|
||||||
return protocol
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from inspect import Parameter
|
|
||||||
except ImportError:
|
|
||||||
class Parameter(object):
|
|
||||||
POSITIONAL_ONLY = object()
|
|
||||||
POSITIONAL_OR_KEYWORD = object()
|
|
||||||
VAR_POSITIONAL = object()
|
|
||||||
KEYWORD_ONLY = object()
|
|
||||||
VAR_KEYWORD = object()
|
|
||||||
|
|
||||||
|
|
||||||
class GeneralizedPopen(subprocess.Popen):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if os.name == 'nt':
|
|
||||||
try:
|
|
||||||
# Was introduced in Python 3.7.
|
|
||||||
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
|
|
||||||
except AttributeError:
|
|
||||||
CREATE_NO_WINDOW = 0x08000000
|
|
||||||
kwargs['creationflags'] = CREATE_NO_WINDOW
|
|
||||||
# The child process doesn't need file descriptors except 0, 1, 2.
|
|
||||||
# This is unix only.
|
|
||||||
kwargs['close_fds'] = 'posix' in sys.builtin_module_names
|
|
||||||
super(GeneralizedPopen, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# shutil.which is not available on Python 2.7.
|
|
||||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
|
||||||
"""Given a command, mode, and a PATH string, return the path which
|
|
||||||
conforms to the given mode on the PATH, or None if there is no such
|
|
||||||
file.
|
|
||||||
|
|
||||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
|
||||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
|
||||||
path.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Check that a given file can be accessed with the correct mode.
|
|
||||||
# Additionally check that `file` is not a directory, as on Windows
|
|
||||||
# directories pass the os.access check.
|
|
||||||
def _access_check(fn, mode):
|
|
||||||
return (os.path.exists(fn) and os.access(fn, mode)
|
|
||||||
and not os.path.isdir(fn))
|
|
||||||
|
|
||||||
# If we're given a path with a directory part, look it up directly rather
|
|
||||||
# than referring to PATH directories. This includes checking relative to the
|
|
||||||
# current directory, e.g. ./script
|
|
||||||
if os.path.dirname(cmd):
|
|
||||||
if _access_check(cmd, mode):
|
|
||||||
return cmd
|
|
||||||
return None
|
|
||||||
|
|
||||||
if path is None:
|
|
||||||
path = os.environ.get("PATH", os.defpath)
|
|
||||||
if not path:
|
|
||||||
return None
|
|
||||||
path = path.split(os.pathsep)
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
|
||||||
# The current directory takes precedence on Windows.
|
|
||||||
if os.curdir not in path:
|
|
||||||
path.insert(0, os.curdir)
|
|
||||||
|
|
||||||
# PATHEXT is necessary to check on Windows.
|
|
||||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
|
||||||
# See if the given file matches any of the expected path extensions.
|
|
||||||
# This will allow us to short circuit when given "python.exe".
|
|
||||||
# If it does match, only test that one, otherwise we have to try
|
|
||||||
# others.
|
|
||||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
|
||||||
files = [cmd]
|
|
||||||
else:
|
|
||||||
files = [cmd + ext for ext in pathext]
|
|
||||||
else:
|
|
||||||
# On other platforms you don't have things like PATHEXT to tell you
|
|
||||||
# what file suffixes are executable, so just pass on cmd as-is.
|
|
||||||
files = [cmd]
|
|
||||||
|
|
||||||
seen = set()
|
|
||||||
for dir in path:
|
|
||||||
normdir = os.path.normcase(dir)
|
|
||||||
if normdir not in seen:
|
|
||||||
seen.add(normdir)
|
|
||||||
for thefile in files:
|
|
||||||
name = os.path.join(dir, thefile)
|
|
||||||
if _access_check(name, mode):
|
|
||||||
return name
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
if not is_py3:
|
|
||||||
# Simplified backport of Python 3 weakref.finalize:
|
|
||||||
# https://github.com/python/cpython/blob/ded4737989316653469763230036b04513cb62b3/Lib/weakref.py#L502-L662
|
|
||||||
class finalize(object):
|
|
||||||
"""Class for finalization of weakrefable objects.
|
|
||||||
|
|
||||||
finalize(obj, func, *args, **kwargs) returns a callable finalizer
|
|
||||||
object which will be called when obj is garbage collected. The
|
|
||||||
first time the finalizer is called it evaluates func(*arg, **kwargs)
|
|
||||||
and returns the result. After this the finalizer is dead, and
|
|
||||||
calling it just returns None.
|
|
||||||
|
|
||||||
When the program exits any remaining finalizers will be run.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Finalizer objects don't have any state of their own.
|
|
||||||
# This ensures that they cannot be part of a ref-cycle.
|
|
||||||
__slots__ = ()
|
|
||||||
_registry = {}
|
|
||||||
|
|
||||||
def __init__(self, obj, func, *args, **kwargs):
|
|
||||||
info = functools.partial(func, *args, **kwargs)
|
|
||||||
info.weakref = weakref.ref(obj, self)
|
|
||||||
self._registry[self] = info
|
|
||||||
|
|
||||||
# To me it's an absolute mystery why in Python 2 we need _=None. It
|
|
||||||
# makes really no sense since it's never really called. Then again it
|
|
||||||
# might be called by Python 2.7 itself, but weakref.finalize is not
|
|
||||||
# documented in Python 2 and therefore shouldn't be randomly called.
|
|
||||||
# We never call this stuff with a parameter and therefore this
|
|
||||||
# parameter should not be needed. But it is. ~dave
|
|
||||||
def __call__(self, _=None):
|
|
||||||
"""Return func(*args, **kwargs) if alive."""
|
|
||||||
info = self._registry.pop(self, None)
|
|
||||||
if info:
|
|
||||||
return info()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _exitfunc(cls):
|
|
||||||
if not cls._registry:
|
|
||||||
return
|
|
||||||
for finalizer in list(cls._registry):
|
|
||||||
try:
|
|
||||||
finalizer()
|
|
||||||
except Exception:
|
|
||||||
sys.excepthook(*sys.exc_info())
|
|
||||||
assert finalizer not in cls._registry
|
|
||||||
|
|
||||||
atexit.register(finalize._exitfunc)
|
|
||||||
weakref.finalize = finalize
|
|
||||||
|
|
||||||
|
|
||||||
if is_py3 and sys.version_info[1] > 5:
|
|
||||||
from inspect import unwrap
|
|
||||||
else:
|
|
||||||
# Only Python >=3.6 does properly limit the amount of unwraps. This is very
|
|
||||||
# relevant in the case of unittest.mock.patch.
|
|
||||||
# Below is the implementation of Python 3.7.
|
|
||||||
def unwrap(func, stop=None):
|
|
||||||
"""Get the object wrapped by *func*.
|
|
||||||
|
|
||||||
Follows the chain of :attr:`__wrapped__` attributes returning the last
|
|
||||||
object in the chain.
|
|
||||||
|
|
||||||
*stop* is an optional callback accepting an object in the wrapper chain
|
|
||||||
as its sole argument that allows the unwrapping to be terminated early if
|
|
||||||
the callback returns a true value. If the callback never returns a true
|
|
||||||
value, the last object in the chain is returned as usual. For example,
|
|
||||||
:func:`signature` uses this to stop unwrapping if any object in the
|
|
||||||
chain has a ``__signature__`` attribute defined.
|
|
||||||
|
|
||||||
:exc:`ValueError` is raised if a cycle is encountered.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if stop is None:
|
|
||||||
def _is_wrapper(f):
|
|
||||||
return hasattr(f, '__wrapped__')
|
|
||||||
else:
|
|
||||||
def _is_wrapper(f):
|
|
||||||
return hasattr(f, '__wrapped__') and not stop(f)
|
|
||||||
f = func # remember the original func for error reporting
|
|
||||||
# Memoise by id to tolerate non-hashable objects, but store objects to
|
|
||||||
# ensure they aren't destroyed, which would allow their IDs to be reused.
|
|
||||||
memo = {id(f): f}
|
|
||||||
recursion_limit = sys.getrecursionlimit()
|
|
||||||
while _is_wrapper(func):
|
|
||||||
func = func.__wrapped__
|
|
||||||
id_func = id(func)
|
|
||||||
if (id_func in memo) or (len(memo) >= recursion_limit):
|
|
||||||
raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
|
|
||||||
memo[id_func] = func
|
|
||||||
return func
|
|
||||||
+353
-203
@@ -6,17 +6,14 @@ Additionally you can add a debug function with :func:`set_debug_function`.
|
|||||||
Alternatively, if you don't need a custom function and are happy with printing
|
Alternatively, if you don't need a custom function and are happy with printing
|
||||||
debug messages to stdout, simply call :func:`set_debug_function` without
|
debug messages to stdout, simply call :func:`set_debug_function` without
|
||||||
arguments.
|
arguments.
|
||||||
|
|
||||||
.. warning:: Please, note that Jedi is **not thread safe**.
|
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
from pathlib import Path
|
||||||
|
|
||||||
import parso
|
import parso
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
|
|
||||||
from jedi._compatibility import force_unicode, cast_path, is_py3
|
from jedi._compatibility import cast_path
|
||||||
from jedi.parser_utils import get_executable_nodes
|
from jedi.parser_utils import get_executable_nodes
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
@@ -26,15 +23,18 @@ from jedi.api import classes
|
|||||||
from jedi.api import interpreter
|
from jedi.api import interpreter
|
||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.api.helpers import validate_line_column
|
from jedi.api.helpers import validate_line_column
|
||||||
from jedi.api.completion import Completion
|
from jedi.api.completion import Completion, search_in_module
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
from jedi.api.environment import InterpreterEnvironment
|
from jedi.api.environment import InterpreterEnvironment
|
||||||
from jedi.api.project import get_default_project, Project
|
from jedi.api.project import get_default_project, Project
|
||||||
|
from jedi.api.errors import parso_to_jedi_errors
|
||||||
|
from jedi.api import refactoring
|
||||||
|
from jedi.api.refactoring.extract import extract_function, extract_variable
|
||||||
from jedi.inference import InferenceState
|
from jedi.inference import InferenceState
|
||||||
from jedi.inference import imports
|
from jedi.inference import imports
|
||||||
from jedi.inference.references import find_references
|
from jedi.inference.references import find_references
|
||||||
from jedi.inference.arguments import try_iter_content
|
from jedi.inference.arguments import try_iter_content
|
||||||
from jedi.inference.helpers import get_module_names, infer_call_of_leaf
|
from jedi.inference.helpers import infer_call_of_leaf
|
||||||
from jedi.inference.sys_path import transform_path_to_dotted
|
from jedi.inference.sys_path import transform_path_to_dotted
|
||||||
from jedi.inference.syntax_tree import tree_name_to_values
|
from jedi.inference.syntax_tree import tree_name_to_values
|
||||||
from jedi.inference.value import ModuleValue
|
from jedi.inference.value import ModuleValue
|
||||||
@@ -42,93 +42,94 @@ from jedi.inference.base_value import ValueSet
|
|||||||
from jedi.inference.value.iterable import unpack_tuple_to_dict
|
from jedi.inference.value.iterable import unpack_tuple_to_dict
|
||||||
from jedi.inference.gradual.conversion import convert_names, convert_values
|
from jedi.inference.gradual.conversion import convert_names, convert_values
|
||||||
from jedi.inference.gradual.utils import load_proper_stub_module
|
from jedi.inference.gradual.utils import load_proper_stub_module
|
||||||
|
from jedi.inference.utils import to_list
|
||||||
|
|
||||||
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
||||||
# can remove some "maximum recursion depth" errors.
|
# can remove some "maximum recursion depth" errors.
|
||||||
sys.setrecursionlimit(3000)
|
sys.setrecursionlimit(3000)
|
||||||
|
|
||||||
|
|
||||||
class Script(object):
|
class Script:
|
||||||
"""
|
"""
|
||||||
A Script is the base for completions, goto or whatever you want to do with
|
A Script is the base for completions, goto or whatever you want to do with
|
||||||
|jedi|.
|
Jedi. The counter part of this class is :class:`Interpreter`, which works
|
||||||
|
with actual dictionaries and can work with a REPL. This class
|
||||||
|
should be used when a user edits code in an editor.
|
||||||
|
|
||||||
You can either use the ``source`` parameter or ``path`` to read a file.
|
You can either use the ``code`` parameter or ``path`` to read a file.
|
||||||
Usually you're going to want to use both of them (in an editor).
|
Usually you're going to want to use both of them (in an editor).
|
||||||
|
|
||||||
The script might be analyzed in a different ``sys.path`` than |jedi|:
|
The Script's ``sys.path`` is very customizable:
|
||||||
|
|
||||||
- if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
|
- If `project` is provided with a ``sys_path``, that is going to be used.
|
||||||
for the script;
|
- If `environment` is provided, its ``sys.path`` will be used
|
||||||
|
(see :func:`Environment.get_sys_path <jedi.api.environment.Environment.get_sys_path>`);
|
||||||
|
- Otherwise ``sys.path`` will match that of the default environment of
|
||||||
|
Jedi, which typically matches the sys path that was used at the time
|
||||||
|
when Jedi was imported.
|
||||||
|
|
||||||
- if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
|
Most methods have a ``line`` and a ``column`` parameter. Lines in Jedi are
|
||||||
variable is defined, ``sys.path`` for the specified environment will be
|
always 1-based and columns are always zero based. To avoid repetition they
|
||||||
guessed (see :func:`jedi.inference.sys_path.get_venv_path`) and used for
|
are not always documented. You can omit both line and column. Jedi will
|
||||||
the script;
|
then just do whatever action you are calling at the end of the file. If you
|
||||||
|
provide only the line, just will complete at the end of that line.
|
||||||
|
|
||||||
- otherwise ``sys.path`` will match that of |jedi|.
|
.. warning:: By default :attr:`jedi.settings.fast_parser` is enabled, which means
|
||||||
|
that parso reuses modules (i.e. they are not immutable). With this setting
|
||||||
|
Jedi is **not thread safe** and it is also not safe to use multiple
|
||||||
|
:class:`.Script` instances and its definitions at the same time.
|
||||||
|
|
||||||
:param source: The source code of the current file, separated by newlines.
|
If you are a normal plugin developer this should not be an issue. It is
|
||||||
:type source: str
|
an issue for people that do more complex stuff with Jedi.
|
||||||
:param line: Deprecated, please use it directly on e.g. `.complete`
|
|
||||||
:type line: int
|
This is purely a performance optimization and works pretty well for all
|
||||||
:param column: Deprecated, please use it directly on e.g. `.complete`
|
typical usages, however consider to turn the setting off if it causes
|
||||||
:type column: int
|
you problems. See also
|
||||||
|
`this discussion <https://github.com/davidhalter/jedi/issues/1240>`_.
|
||||||
|
|
||||||
|
:param code: The source code of the current file, separated by newlines.
|
||||||
|
:type code: str
|
||||||
:param path: The path of the file in the file system, or ``''`` if
|
:param path: The path of the file in the file system, or ``''`` if
|
||||||
it hasn't been saved yet.
|
it hasn't been saved yet.
|
||||||
:type path: str or None
|
:type path: str or pathlib.Path or None
|
||||||
:param encoding: The encoding of ``source``, if it is not a
|
:param Environment environment: Provide a predefined :ref:`Environment <environments>`
|
||||||
``unicode`` object (default ``'utf-8'``).
|
to work with a specific Python version or virtualenv.
|
||||||
:type encoding: str
|
:param Project project: Provide a :class:`.Project` to make sure finding
|
||||||
:param sys_path: ``sys.path`` to use during analysis of the script
|
references works well, because the right folder is searched. There are
|
||||||
:type sys_path: list
|
also ways to modify the sys path and other things.
|
||||||
:param environment: TODO
|
|
||||||
:type environment: Environment
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, source=None, line=None, column=None, path=None,
|
def __init__(self, code=None, *, path=None, environment=None, project=None):
|
||||||
encoding='utf-8', sys_path=None, environment=None,
|
|
||||||
_project=None):
|
|
||||||
self._orig_path = path
|
self._orig_path = path
|
||||||
# An empty path (also empty string) should always result in no path.
|
# An empty path (also empty string) should always result in no path.
|
||||||
self.path = os.path.abspath(path) if path else None
|
if isinstance(path, str):
|
||||||
|
path = Path(path)
|
||||||
|
|
||||||
if source is None:
|
self.path = path.absolute() if path else None
|
||||||
|
|
||||||
|
if code is None:
|
||||||
# TODO add a better warning than the traceback!
|
# TODO add a better warning than the traceback!
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
source = f.read()
|
code = f.read()
|
||||||
|
|
||||||
# Load the Python grammar of the current interpreter.
|
|
||||||
self._grammar = parso.load_grammar()
|
|
||||||
|
|
||||||
if sys_path is not None and not is_py3:
|
|
||||||
sys_path = list(map(force_unicode, sys_path))
|
|
||||||
|
|
||||||
project = _project
|
|
||||||
if project is None:
|
if project is None:
|
||||||
# Load the Python grammar of the current interpreter.
|
# Load the Python grammar of the current interpreter.
|
||||||
project = get_default_project(
|
project = get_default_project(None if self.path is None else self.path.parent)
|
||||||
os.path.dirname(self.path)if path else os.getcwd()
|
|
||||||
)
|
|
||||||
# TODO deprecate and remove sys_path from the Script API.
|
|
||||||
if sys_path is not None:
|
|
||||||
project._sys_path = sys_path
|
|
||||||
self._inference_state = InferenceState(
|
self._inference_state = InferenceState(
|
||||||
project, environment=environment, script_path=self.path
|
project, environment=environment, script_path=self.path
|
||||||
)
|
)
|
||||||
debug.speed('init')
|
debug.speed('init')
|
||||||
self._module_node, source = self._inference_state.parse_and_get_code(
|
self._module_node, code = self._inference_state.parse_and_get_code(
|
||||||
code=source,
|
code=code,
|
||||||
path=self.path,
|
path=self.path,
|
||||||
encoding=encoding,
|
use_latest_grammar=path and path.suffix == '.pyi',
|
||||||
use_latest_grammar=path and path.endswith('.pyi'),
|
|
||||||
cache=False, # No disk cache, because the current script often changes.
|
cache=False, # No disk cache, because the current script often changes.
|
||||||
diff_cache=settings.fast_parser,
|
diff_cache=settings.fast_parser,
|
||||||
cache_path=settings.cache_directory,
|
cache_path=settings.cache_directory,
|
||||||
)
|
)
|
||||||
debug.speed('parsed')
|
debug.speed('parsed')
|
||||||
self._code_lines = parso.split_lines(source, keepends=True)
|
self._code_lines = parso.split_lines(code, keepends=True)
|
||||||
self._code = source
|
self._code = code
|
||||||
self._pos = line, column
|
|
||||||
|
|
||||||
cache.clear_time_caches()
|
cache.clear_time_caches()
|
||||||
debug.reset_time()
|
debug.reset_time()
|
||||||
@@ -152,10 +153,11 @@ class Script(object):
|
|||||||
file_io = None
|
file_io = None
|
||||||
else:
|
else:
|
||||||
file_io = KnownContentFileIO(cast_path(self.path), self._code)
|
file_io = KnownContentFileIO(cast_path(self.path), self._code)
|
||||||
if self.path is not None and self.path.endswith('.pyi'):
|
if self.path is not None and self.path.suffix == '.pyi':
|
||||||
# We are in a stub file. Try to load the stub properly.
|
# We are in a stub file. Try to load the stub properly.
|
||||||
stub_module = load_proper_stub_module(
|
stub_module = load_proper_stub_module(
|
||||||
self._inference_state,
|
self._inference_state,
|
||||||
|
self._inference_state.latest_grammar,
|
||||||
file_io,
|
file_io,
|
||||||
names,
|
names,
|
||||||
self._module_node
|
self._module_node
|
||||||
@@ -173,7 +175,7 @@ class Script(object):
|
|||||||
code_lines=self._code_lines,
|
code_lines=self._code_lines,
|
||||||
is_package=is_package,
|
is_package=is_package,
|
||||||
)
|
)
|
||||||
if names[0] not in ('builtins', '__builtin__', 'typing'):
|
if names[0] not in ('builtins', 'typing'):
|
||||||
# These modules are essential for Jedi, so don't overwrite them.
|
# These modules are essential for Jedi, so don't overwrite them.
|
||||||
self._inference_state.module_cache.add(names, ValueSet([module]))
|
self._inference_state.module_cache.add(names, ValueSet([module]))
|
||||||
return module
|
return module
|
||||||
@@ -189,19 +191,20 @@ class Script(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def complete(self, line=None, column=None, **kwargs):
|
def complete(self, line=None, column=None, *, fuzzy=False):
|
||||||
"""
|
"""
|
||||||
Return :class:`classes.Completion` objects. Those objects contain
|
Completes objects under the cursor.
|
||||||
information about the completions, more than just names.
|
|
||||||
|
Those objects contain information about the completions, more than just
|
||||||
|
names.
|
||||||
|
|
||||||
:param fuzzy: Default False. Will return fuzzy completions, which means
|
:param fuzzy: Default False. Will return fuzzy completions, which means
|
||||||
that e.g. ``ooa`` will match ``foobar``.
|
that e.g. ``ooa`` will match ``foobar``.
|
||||||
:return: Completion objects, sorted by name and ``__`` comes last.
|
:return: Completion objects, sorted by name. Normal names appear
|
||||||
:rtype: list of :class:`classes.Completion`
|
before "private" names that start with ``_`` and those appear
|
||||||
|
before magic methods and name mangled names that start with ``__``.
|
||||||
|
:rtype: list of :class:`.Completion`
|
||||||
"""
|
"""
|
||||||
return self._complete(line, column, **kwargs)
|
|
||||||
|
|
||||||
def _complete(self, line, column, fuzzy=False): # Python 2...
|
|
||||||
with debug.increase_indent_cm('complete'):
|
with debug.increase_indent_cm('complete'):
|
||||||
completion = Completion(
|
completion = Completion(
|
||||||
self._inference_state, self._get_module_context(), self._code_lines,
|
self._inference_state, self._get_module_context(), self._code_lines,
|
||||||
@@ -209,40 +212,34 @@ class Script(object):
|
|||||||
)
|
)
|
||||||
return completion.complete()
|
return completion.complete()
|
||||||
|
|
||||||
def completions(self, fuzzy=False):
|
|
||||||
# Deprecated, will be removed.
|
|
||||||
return self.complete(*self._pos, fuzzy=fuzzy)
|
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def infer(self, line=None, column=None, **kwargs):
|
def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
|
||||||
"""
|
"""
|
||||||
Return the definitions of a the path under the cursor. goto function!
|
Return the definitions of under the cursor. It is basically a wrapper
|
||||||
This follows complicated paths and returns the end, not the first
|
around Jedi's type inference.
|
||||||
definition. The big difference between :meth:`goto` and
|
|
||||||
|
This method follows complicated paths and returns the end, not the
|
||||||
|
first definition. The big difference between :meth:`goto` and
|
||||||
:meth:`infer` is that :meth:`goto` doesn't
|
:meth:`infer` is that :meth:`goto` doesn't
|
||||||
follow imports and statements. Multiple objects may be returned,
|
follow imports and statements. Multiple objects may be returned,
|
||||||
because Python itself is a dynamic language, which means depending on
|
because depending on an option you can have two different versions of a
|
||||||
an option you can have two different versions of a function.
|
function.
|
||||||
|
|
||||||
:param only_stubs: Only return stubs for this goto call.
|
:param only_stubs: Only return stubs for this method.
|
||||||
:param prefer_stubs: Prefer stubs to Python objects for this type
|
:param prefer_stubs: Prefer stubs to Python objects for this method.
|
||||||
inference call.
|
:rtype: list of :class:`.Name`
|
||||||
:rtype: list of :class:`classes.Definition`
|
|
||||||
"""
|
"""
|
||||||
with debug.increase_indent_cm('infer'):
|
|
||||||
return self._infer(line, column, **kwargs)
|
|
||||||
|
|
||||||
def goto_definitions(self, **kwargs):
|
|
||||||
# Deprecated, will be removed.
|
|
||||||
return self.infer(*self._pos, **kwargs)
|
|
||||||
|
|
||||||
def _infer(self, line, column, only_stubs=False, prefer_stubs=False):
|
|
||||||
pos = line, column
|
pos = line, column
|
||||||
leaf = self._module_node.get_name_of_position(pos)
|
leaf = self._module_node.get_name_of_position(pos)
|
||||||
if leaf is None:
|
if leaf is None:
|
||||||
leaf = self._module_node.get_leaf_for_position(pos)
|
leaf = self._module_node.get_leaf_for_position(pos)
|
||||||
if leaf is None or leaf.type == 'string':
|
if leaf is None or leaf.type == 'string':
|
||||||
return []
|
return []
|
||||||
|
if leaf.end_pos == (line, column) and leaf.type == 'operator':
|
||||||
|
next_ = leaf.get_next_leaf()
|
||||||
|
if next_.start_pos == leaf.end_pos \
|
||||||
|
and next_.type in ('number', 'string', 'keyword'):
|
||||||
|
leaf = next_
|
||||||
|
|
||||||
context = self._get_module_context().create_context(leaf)
|
context = self._get_module_context().create_context(leaf)
|
||||||
|
|
||||||
@@ -253,39 +250,28 @@ class Script(object):
|
|||||||
prefer_stubs=prefer_stubs,
|
prefer_stubs=prefer_stubs,
|
||||||
)
|
)
|
||||||
|
|
||||||
defs = [classes.Definition(self._inference_state, c.name) for c in values]
|
defs = [classes.Name(self._inference_state, c.name) for c in values]
|
||||||
# The additional set here allows the definitions to become unique in an
|
# The additional set here allows the definitions to become unique in an
|
||||||
# API sense. In the internals we want to separate more things than in
|
# API sense. In the internals we want to separate more things than in
|
||||||
# the API.
|
# the API.
|
||||||
return helpers.sorted_definitions(set(defs))
|
return helpers.sorted_definitions(set(defs))
|
||||||
|
|
||||||
def goto_assignments(self, follow_imports=False, follow_builtin_imports=False, **kwargs):
|
|
||||||
# Deprecated, will be removed.
|
|
||||||
return self.goto(*self._pos,
|
|
||||||
follow_imports=follow_imports,
|
|
||||||
follow_builtin_imports=follow_builtin_imports,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def goto(self, line=None, column=None, **kwargs):
|
def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
|
||||||
|
only_stubs=False, prefer_stubs=False):
|
||||||
"""
|
"""
|
||||||
Return the first definition found, while optionally following imports.
|
Goes to the name that defined the object under the cursor. Optionally
|
||||||
Multiple objects may be returned, because Python itself is a
|
you can follow imports.
|
||||||
dynamic language, which means depending on an option you can have two
|
Multiple objects may be returned, depending on an if you can have two
|
||||||
different versions of a function.
|
different versions of a function.
|
||||||
|
|
||||||
:param follow_imports: The goto call will follow imports.
|
:param follow_imports: The method will follow imports.
|
||||||
:param follow_builtin_imports: If follow_imports is True will decide if
|
:param follow_builtin_imports: If ``follow_imports`` is True will try
|
||||||
it follow builtin imports.
|
to look up names in builtins (i.e. compiled or extension modules).
|
||||||
:param only_stubs: Only return stubs for this goto call.
|
:param only_stubs: Only return stubs for this method.
|
||||||
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
|
:param prefer_stubs: Prefer stubs to Python objects for this method.
|
||||||
:rtype: list of :class:`classes.Definition`
|
:rtype: list of :class:`.Name`
|
||||||
"""
|
"""
|
||||||
with debug.increase_indent_cm('goto'):
|
|
||||||
return self._goto(line, column, **kwargs)
|
|
||||||
|
|
||||||
def _goto(self, line, column, follow_imports=False, follow_builtin_imports=False,
|
|
||||||
only_stubs=False, prefer_stubs=False):
|
|
||||||
tree_name = self._module_node.get_name_of_position((line, column))
|
tree_name = self._module_node.get_name_of_position((line, column))
|
||||||
if tree_name is None:
|
if tree_name is None:
|
||||||
# Without a name we really just want to jump to the result e.g.
|
# Without a name we really just want to jump to the result e.g.
|
||||||
@@ -310,78 +296,130 @@ class Script(object):
|
|||||||
names = list(name.goto())
|
names = list(name.goto())
|
||||||
|
|
||||||
if follow_imports:
|
if follow_imports:
|
||||||
names = helpers.filter_follow_imports(names)
|
names = helpers.filter_follow_imports(names, follow_builtin_imports)
|
||||||
names = convert_names(
|
names = convert_names(
|
||||||
names,
|
names,
|
||||||
only_stubs=only_stubs,
|
only_stubs=only_stubs,
|
||||||
prefer_stubs=prefer_stubs,
|
prefer_stubs=prefer_stubs,
|
||||||
)
|
)
|
||||||
|
|
||||||
defs = [classes.Definition(self._inference_state, d) for d in set(names)]
|
defs = [classes.Name(self._inference_state, d) for d in set(names)]
|
||||||
return helpers.sorted_definitions(defs)
|
# Avoid duplicates
|
||||||
|
return list(set(helpers.sorted_definitions(defs)))
|
||||||
|
|
||||||
|
def search(self, string, *, all_scopes=False):
|
||||||
|
"""
|
||||||
|
Searches a name in the current file. For a description of how the
|
||||||
|
search string should look like, please have a look at
|
||||||
|
:meth:`.Project.search`.
|
||||||
|
|
||||||
|
:param bool all_scopes: Default False; searches not only for
|
||||||
|
definitions on the top level of a module level, but also in
|
||||||
|
functions and classes.
|
||||||
|
:yields: :class:`.Name`
|
||||||
|
"""
|
||||||
|
return self._search_func(string, all_scopes=all_scopes)
|
||||||
|
|
||||||
|
@to_list
|
||||||
|
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
|
||||||
|
names = self._names(all_scopes=all_scopes)
|
||||||
|
wanted_type, wanted_names = helpers.split_search_string(string)
|
||||||
|
return search_in_module(
|
||||||
|
self._inference_state,
|
||||||
|
self._get_module_context(),
|
||||||
|
names=names,
|
||||||
|
wanted_type=wanted_type,
|
||||||
|
wanted_names=wanted_names,
|
||||||
|
complete=complete,
|
||||||
|
fuzzy=fuzzy,
|
||||||
|
)
|
||||||
|
|
||||||
|
def complete_search(self, string, **kwargs):
|
||||||
|
"""
|
||||||
|
Like :meth:`.Script.search`, but completes that string. If you want to
|
||||||
|
have all possible definitions in a file you can also provide an empty
|
||||||
|
string.
|
||||||
|
|
||||||
|
:param bool all_scopes: Default False; searches not only for
|
||||||
|
definitions on the top level of a module level, but also in
|
||||||
|
functions and classes.
|
||||||
|
:param fuzzy: Default False. Will return fuzzy completions, which means
|
||||||
|
that e.g. ``ooa`` will match ``foobar``.
|
||||||
|
:yields: :class:`.Completion`
|
||||||
|
"""
|
||||||
|
return self._search_func(string, complete=True, **kwargs)
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def help(self, line=None, column=None):
|
def help(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
Works like goto and returns a list of Definition objects. Returns
|
Used to display a help window to users. Uses :meth:`.Script.goto` and
|
||||||
additional definitions for keywords and operators.
|
returns additional definitions for keywords and operators.
|
||||||
|
|
||||||
The additional definitions are of ``Definition(...).type == 'keyword'``.
|
Typically you will want to display :meth:`.BaseName.docstring` to the
|
||||||
|
user for all the returned definitions.
|
||||||
|
|
||||||
|
The additional definitions are ``Name(...).type == 'keyword'``.
|
||||||
These definitions do not have a lot of value apart from their docstring
|
These definitions do not have a lot of value apart from their docstring
|
||||||
attribute, which contains the output of Python's ``help()`` function.
|
attribute, which contains the output of Python's :func:`help` function.
|
||||||
|
|
||||||
:rtype: list of :class:`classes.Definition`
|
:rtype: list of :class:`.Name`
|
||||||
"""
|
"""
|
||||||
definitions = self.goto(line, column, follow_imports=True)
|
definitions = self.goto(line, column, follow_imports=True)
|
||||||
if definitions:
|
if definitions:
|
||||||
return definitions
|
return definitions
|
||||||
leaf = self._module_node.get_leaf_for_position((line, column))
|
leaf = self._module_node.get_leaf_for_position((line, column))
|
||||||
if leaf.type in ('keyword', 'operator', 'error_leaf'):
|
if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
|
||||||
reserved = self._grammar._pgen_grammar.reserved_syntax_strings.keys()
|
def need_pydoc():
|
||||||
if leaf.value in reserved:
|
if leaf.value in ('(', ')', '[', ']'):
|
||||||
name = KeywordName(self._inference_state, leaf.value)
|
if leaf.parent.type == 'trailer':
|
||||||
return [classes.Definition(self._inference_state, name)]
|
return False
|
||||||
return []
|
if leaf.parent.type == 'atom':
|
||||||
|
return False
|
||||||
|
grammar = self._inference_state.grammar
|
||||||
|
# This parso stuff is not public, but since I control it, this
|
||||||
|
# is fine :-) ~dave
|
||||||
|
reserved = grammar._pgen_grammar.reserved_syntax_strings.keys()
|
||||||
|
return leaf.value in reserved
|
||||||
|
|
||||||
def usages(self, **kwargs):
|
if need_pydoc():
|
||||||
# Deprecated, will be removed.
|
name = KeywordName(self._inference_state, leaf.value)
|
||||||
return self.get_references(*self._pos, **kwargs)
|
return [classes.Name(self._inference_state, name)]
|
||||||
|
return []
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_references(self, line=None, column=None, **kwargs):
|
def get_references(self, line=None, column=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return :class:`classes.Definition` objects, which contain all
|
Lists all references of a variable in a project. Since this can be
|
||||||
names that point to the definition of the name under the cursor. This
|
quite hard to do for Jedi, if it is too complicated, Jedi will stop
|
||||||
is very useful for refactoring (renaming), or to show all references of
|
searching.
|
||||||
a variable.
|
|
||||||
|
|
||||||
:param include_builtins: Default True, checks if a reference is a
|
:param include_builtins: Default ``True``. If ``False``, checks if a definition
|
||||||
builtin (e.g. ``sys``) and in that case does not return it.
|
is a builtin (e.g. ``sys``) and in that case does not return it.
|
||||||
:rtype: list of :class:`classes.Definition`
|
:param scope: Default ``'project'``. If ``'file'``, include references in
|
||||||
|
the current module only.
|
||||||
|
:rtype: list of :class:`.Name`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _references(include_builtins=True):
|
def _references(include_builtins=True, scope='project'):
|
||||||
|
if scope not in ('project', 'file'):
|
||||||
|
raise ValueError('Only the scopes "file" and "project" are allowed')
|
||||||
tree_name = self._module_node.get_name_of_position((line, column))
|
tree_name = self._module_node.get_name_of_position((line, column))
|
||||||
if tree_name is None:
|
if tree_name is None:
|
||||||
# Must be syntax
|
# Must be syntax
|
||||||
return []
|
return []
|
||||||
|
|
||||||
names = find_references(self._get_module_context(), tree_name)
|
names = find_references(self._get_module_context(), tree_name, scope == 'file')
|
||||||
|
|
||||||
definitions = [classes.Definition(self._inference_state, n) for n in names]
|
definitions = [classes.Name(self._inference_state, n) for n in names]
|
||||||
if not include_builtins:
|
if not include_builtins or scope == 'file':
|
||||||
definitions = [d for d in definitions if not d.in_builtin_module()]
|
definitions = [d for d in definitions if not d.in_builtin_module()]
|
||||||
return helpers.sorted_definitions(definitions)
|
return helpers.sorted_definitions(definitions)
|
||||||
return _references(**kwargs)
|
return _references(**kwargs)
|
||||||
|
|
||||||
def call_signatures(self):
|
|
||||||
# Deprecated, will be removed.
|
|
||||||
return self.get_signatures(*self._pos)
|
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_signatures(self, line=None, column=None):
|
def get_signatures(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
Return the function object of the call you're currently in.
|
Return the function object of the call under the cursor.
|
||||||
|
|
||||||
E.g. if the cursor is here::
|
E.g. if the cursor is here::
|
||||||
|
|
||||||
@@ -393,7 +431,7 @@ class Script(object):
|
|||||||
|
|
||||||
This would return an empty list..
|
This would return an empty list..
|
||||||
|
|
||||||
:rtype: list of :class:`classes.Signature`
|
:rtype: list of :class:`.Signature`
|
||||||
"""
|
"""
|
||||||
pos = line, column
|
pos = line, column
|
||||||
call_details = helpers.get_signature_details(self._module_node, pos)
|
call_details = helpers.get_signature_details(self._module_node, pos)
|
||||||
@@ -417,6 +455,12 @@ class Script(object):
|
|||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_context(self, line=None, column=None):
|
def get_context(self, line=None, column=None):
|
||||||
|
"""
|
||||||
|
Returns the scope context under the cursor. This basically means the
|
||||||
|
function, class or module where the cursor is at.
|
||||||
|
|
||||||
|
:rtype: :class:`.Name`
|
||||||
|
"""
|
||||||
pos = (line, column)
|
pos = (line, column)
|
||||||
leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
|
leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
|
||||||
if leaf.start_pos > pos or leaf.type == 'endmarker':
|
if leaf.start_pos > pos or leaf.type == 'endmarker':
|
||||||
@@ -439,7 +483,7 @@ class Script(object):
|
|||||||
while context.name is None:
|
while context.name is None:
|
||||||
context = context.parent_context # comprehensions
|
context = context.parent_context # comprehensions
|
||||||
|
|
||||||
definition = classes.Definition(self._inference_state, context.name)
|
definition = classes.Name(self._inference_state, context.name)
|
||||||
while definition.type != 'module':
|
while definition.type != 'module':
|
||||||
name = definition._name # TODO private access
|
name = definition._name # TODO private access
|
||||||
tree_name = name.tree_name
|
tree_name = name.tree_name
|
||||||
@@ -486,66 +530,186 @@ class Script(object):
|
|||||||
|
|
||||||
def get_names(self, **kwargs):
|
def get_names(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a list of `Definition` objects, containing name parts.
|
Returns names defined in the current file.
|
||||||
This means you can call ``Definition.goto()`` and get the
|
|
||||||
reference of a name.
|
|
||||||
|
|
||||||
:param all_scopes: If True lists the names of all scopes instead of only
|
:param all_scopes: If True lists the names of all scopes instead of
|
||||||
the module namespace.
|
only the module namespace.
|
||||||
:param definitions: If True lists the names that have been defined by a
|
:param definitions: If True lists the names that have been defined by a
|
||||||
class, function or a statement (``a = b`` returns ``a``).
|
class, function or a statement (``a = b`` returns ``a``).
|
||||||
:param references: If True lists all the names that are not listed by
|
:param references: If True lists all the names that are not listed by
|
||||||
``definitions=True``. E.g. ``a = b`` returns ``b``.
|
``definitions=True``. E.g. ``a = b`` returns ``b``.
|
||||||
|
:rtype: list of :class:`.Name`
|
||||||
"""
|
"""
|
||||||
return self._names(**kwargs) # Python 2...
|
names = self._names(**kwargs)
|
||||||
|
return [classes.Name(self._inference_state, n) for n in names]
|
||||||
|
|
||||||
|
def get_syntax_errors(self):
|
||||||
|
"""
|
||||||
|
Lists all syntax errors in the current file.
|
||||||
|
|
||||||
|
:rtype: list of :class:`.SyntaxError`
|
||||||
|
"""
|
||||||
|
return parso_to_jedi_errors(self._inference_state.grammar, self._module_node)
|
||||||
|
|
||||||
def _names(self, all_scopes=False, definitions=True, references=False):
|
def _names(self, all_scopes=False, definitions=True, references=False):
|
||||||
def def_ref_filter(_def):
|
|
||||||
is_def = _def._name.tree_name.is_definition()
|
|
||||||
return definitions and is_def or references and not is_def
|
|
||||||
|
|
||||||
# Set line/column to a random position, because they don't matter.
|
# Set line/column to a random position, because they don't matter.
|
||||||
module_context = self._get_module_context()
|
module_context = self._get_module_context()
|
||||||
defs = [
|
defs = [
|
||||||
classes.Definition(
|
module_context.create_name(name)
|
||||||
self._inference_state,
|
for name in helpers.get_module_names(
|
||||||
module_context.create_name(name)
|
self._module_node,
|
||||||
) for name in get_module_names(self._module_node, all_scopes)
|
all_scopes=all_scopes,
|
||||||
|
definitions=definitions,
|
||||||
|
references=references,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column))
|
return sorted(defs, key=lambda x: x.start_pos)
|
||||||
|
|
||||||
|
def rename(self, line=None, column=None, *, new_name):
|
||||||
|
"""
|
||||||
|
Renames all references of the variable under the cursor.
|
||||||
|
|
||||||
|
:param new_name: The variable under the cursor will be renamed to this
|
||||||
|
string.
|
||||||
|
:raises: :exc:`.RefactoringError`
|
||||||
|
:rtype: :class:`.Refactoring`
|
||||||
|
"""
|
||||||
|
definitions = self.get_references(line, column, include_builtins=False)
|
||||||
|
return refactoring.rename(self._inference_state, definitions, new_name)
|
||||||
|
|
||||||
|
@validate_line_column
|
||||||
|
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
|
||||||
|
"""
|
||||||
|
Moves an expression to a new statemenet.
|
||||||
|
|
||||||
|
For example if you have the cursor on ``foo`` and provide a
|
||||||
|
``new_name`` called ``bar``::
|
||||||
|
|
||||||
|
foo = 3.1
|
||||||
|
x = int(foo + 1)
|
||||||
|
|
||||||
|
the code above will become::
|
||||||
|
|
||||||
|
foo = 3.1
|
||||||
|
bar = foo + 1
|
||||||
|
x = int(bar)
|
||||||
|
|
||||||
|
:param new_name: The expression under the cursor will be renamed to
|
||||||
|
this string.
|
||||||
|
:param int until_line: The the selection range ends at this line, when
|
||||||
|
omitted, Jedi will be clever and try to define the range itself.
|
||||||
|
:param int until_column: The the selection range ends at this column, when
|
||||||
|
omitted, Jedi will be clever and try to define the range itself.
|
||||||
|
:raises: :exc:`.RefactoringError`
|
||||||
|
:rtype: :class:`.Refactoring`
|
||||||
|
"""
|
||||||
|
if until_line is None and until_column is None:
|
||||||
|
until_pos = None
|
||||||
|
else:
|
||||||
|
if until_line is None:
|
||||||
|
until_line = line
|
||||||
|
if until_column is None:
|
||||||
|
until_column = len(self._code_lines[until_line - 1])
|
||||||
|
until_pos = until_line, until_column
|
||||||
|
return extract_variable(
|
||||||
|
self._inference_state, self.path, self._module_node,
|
||||||
|
new_name, (line, column), until_pos
|
||||||
|
)
|
||||||
|
|
||||||
|
@validate_line_column
|
||||||
|
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
|
||||||
|
"""
|
||||||
|
Moves an expression to a new function.
|
||||||
|
|
||||||
|
For example if you have the cursor on ``foo`` and provide a
|
||||||
|
``new_name`` called ``bar``::
|
||||||
|
|
||||||
|
global_var = 3
|
||||||
|
|
||||||
|
def x():
|
||||||
|
foo = 3.1
|
||||||
|
x = int(foo + 1 + global_var)
|
||||||
|
|
||||||
|
the code above will become::
|
||||||
|
|
||||||
|
global_var = 3
|
||||||
|
|
||||||
|
def bar(foo):
|
||||||
|
return int(foo + 1 + global_var)
|
||||||
|
|
||||||
|
def x():
|
||||||
|
foo = 3.1
|
||||||
|
x = bar(foo)
|
||||||
|
|
||||||
|
:param new_name: The expression under the cursor will be replaced with
|
||||||
|
a function with this name.
|
||||||
|
:param int until_line: The the selection range ends at this line, when
|
||||||
|
omitted, Jedi will be clever and try to define the range itself.
|
||||||
|
:param int until_column: The the selection range ends at this column, when
|
||||||
|
omitted, Jedi will be clever and try to define the range itself.
|
||||||
|
:raises: :exc:`.RefactoringError`
|
||||||
|
:rtype: :class:`.Refactoring`
|
||||||
|
"""
|
||||||
|
if until_line is None and until_column is None:
|
||||||
|
until_pos = None
|
||||||
|
else:
|
||||||
|
if until_line is None:
|
||||||
|
until_line = line
|
||||||
|
if until_column is None:
|
||||||
|
until_column = len(self._code_lines[until_line - 1])
|
||||||
|
until_pos = until_line, until_column
|
||||||
|
return extract_function(
|
||||||
|
self._inference_state, self.path, self._get_module_context(),
|
||||||
|
new_name, (line, column), until_pos
|
||||||
|
)
|
||||||
|
|
||||||
|
def inline(self, line=None, column=None):
|
||||||
|
"""
|
||||||
|
Inlines a variable under the cursor. This is basically the opposite of
|
||||||
|
extracting a variable. For example with the cursor on bar::
|
||||||
|
|
||||||
|
foo = 3.1
|
||||||
|
bar = foo + 1
|
||||||
|
x = int(bar)
|
||||||
|
|
||||||
|
the code above will become::
|
||||||
|
|
||||||
|
foo = 3.1
|
||||||
|
x = int(foo + 1)
|
||||||
|
|
||||||
|
:raises: :exc:`.RefactoringError`
|
||||||
|
:rtype: :class:`.Refactoring`
|
||||||
|
"""
|
||||||
|
names = [d._name for d in self.get_references(line, column, include_builtins=True)]
|
||||||
|
return refactoring.inline(self._inference_state, names)
|
||||||
|
|
||||||
|
|
||||||
class Interpreter(Script):
|
class Interpreter(Script):
|
||||||
"""
|
"""
|
||||||
Jedi API for Python REPLs.
|
Jedi's API for Python REPLs.
|
||||||
|
|
||||||
In addition to completion of simple attribute access, Jedi
|
Implements all of the methods that are present in :class:`.Script` as well.
|
||||||
supports code completion based on static code analysis.
|
|
||||||
Jedi can complete attributes of object which is not initialized
|
In addition to completions that normal REPL completion does like
|
||||||
yet.
|
``str.upper``, Jedi also supports code completion based on static code
|
||||||
|
analysis. For example Jedi will complete ``str().upper``.
|
||||||
|
|
||||||
>>> from os.path import join
|
>>> from os.path import join
|
||||||
>>> namespace = locals()
|
>>> namespace = locals()
|
||||||
>>> script = Interpreter('join("").up', [namespace])
|
>>> script = Interpreter('join("").up', [namespace])
|
||||||
>>> print(script.complete()[0].name)
|
>>> print(script.complete()[0].name)
|
||||||
upper
|
upper
|
||||||
|
|
||||||
|
All keyword arguments are same as the arguments for :class:`.Script`.
|
||||||
|
|
||||||
|
:param str code: Code to parse.
|
||||||
|
:type namespaces: typing.List[dict]
|
||||||
|
:param namespaces: A list of namespace dictionaries such as the one
|
||||||
|
returned by :func:`globals` and :func:`locals`.
|
||||||
"""
|
"""
|
||||||
_allow_descriptor_getattr_default = True
|
_allow_descriptor_getattr_default = True
|
||||||
|
|
||||||
def __init__(self, source, namespaces, **kwds):
|
def __init__(self, code, namespaces, **kwds):
|
||||||
"""
|
|
||||||
Parse `source` and mixin interpreted Python objects from `namespaces`.
|
|
||||||
|
|
||||||
:type source: str
|
|
||||||
:arg source: Code to parse.
|
|
||||||
:type namespaces: list of dict
|
|
||||||
:arg namespaces: a list of namespace dictionaries such as the one
|
|
||||||
returned by :func:`locals`.
|
|
||||||
|
|
||||||
Other optional arguments are same as the ones for :class:`Script`.
|
|
||||||
If `line` and `column` are None, they are assumed be at the end of
|
|
||||||
`source`.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
namespaces = [dict(n) for n in namespaces]
|
namespaces = [dict(n) for n in namespaces]
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -558,8 +722,8 @@ class Interpreter(Script):
|
|||||||
if not isinstance(environment, InterpreterEnvironment):
|
if not isinstance(environment, InterpreterEnvironment):
|
||||||
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
|
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
|
||||||
|
|
||||||
super(Interpreter, self).__init__(source, environment=environment,
|
super().__init__(code, environment=environment,
|
||||||
_project=Project(os.getcwd()), **kwds)
|
project=Project(Path.cwd()), **kwds)
|
||||||
self.namespaces = namespaces
|
self.namespaces = namespaces
|
||||||
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
||||||
|
|
||||||
@@ -567,7 +731,7 @@ class Interpreter(Script):
|
|||||||
def _get_module_context(self):
|
def _get_module_context(self):
|
||||||
tree_module_value = ModuleValue(
|
tree_module_value = ModuleValue(
|
||||||
self._inference_state, self._module_node,
|
self._inference_state, self._module_node,
|
||||||
file_io=KnownContentFileIO(self.path, self._code),
|
file_io=KnownContentFileIO(str(self.path), self._code),
|
||||||
string_names=('__main__',),
|
string_names=('__main__',),
|
||||||
code_lines=self._code_lines,
|
code_lines=self._code_lines,
|
||||||
)
|
)
|
||||||
@@ -577,31 +741,17 @@ class Interpreter(Script):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def names(source=None, path=None, encoding='utf-8', all_scopes=False,
|
|
||||||
definitions=True, references=False, environment=None):
|
|
||||||
warnings.warn(
|
|
||||||
"Deprecated since version 0.16.0. Use Script(...).get_names instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
|
|
||||||
return Script(source, path=path, encoding=encoding).get_names(
|
|
||||||
all_scopes=all_scopes,
|
|
||||||
definitions=definitions,
|
|
||||||
references=references,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def preload_module(*modules):
|
def preload_module(*modules):
|
||||||
"""
|
"""
|
||||||
Preloading modules tells Jedi to load a module now, instead of lazy parsing
|
Preloading modules tells Jedi to load a module now, instead of lazy parsing
|
||||||
of modules. Usful for IDEs, to control which modules to load on startup.
|
of modules. This can be useful for IDEs, to control which modules to load
|
||||||
|
on startup.
|
||||||
|
|
||||||
:param modules: different module names, list of string.
|
:param modules: different module names, list of string.
|
||||||
"""
|
"""
|
||||||
for m in modules:
|
for m in modules:
|
||||||
s = "import %s as x; x." % m
|
s = "import %s as x; x." % m
|
||||||
Script(s, path=None).complete(1, len(s))
|
Script(s).complete(1, len(s))
|
||||||
|
|
||||||
|
|
||||||
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
||||||
@@ -611,7 +761,7 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
|||||||
|
|
||||||
If you don't specify any arguments, debug messages will be printed to stdout.
|
If you don't specify any arguments, debug messages will be printed to stdout.
|
||||||
|
|
||||||
:param func_cb: The callback function for debug messages, with n params.
|
:param func_cb: The callback function for debug messages.
|
||||||
"""
|
"""
|
||||||
debug.debug_function = func_cb
|
debug.debug_function = func_cb
|
||||||
debug.enable_warning = warnings
|
debug.enable_warning = warnings
|
||||||
|
|||||||
+254
-166
@@ -1,21 +1,31 @@
|
|||||||
"""
|
"""
|
||||||
The :mod:`jedi.api.classes` module contains the return classes of the API.
|
There are a couple of classes documented in here:
|
||||||
These classes are the much bigger part of the whole API, because they contain
|
|
||||||
the interesting information about completion and goto operations.
|
- :class:`.BaseName` as an abstact base class for almost everything.
|
||||||
|
- :class:`.Name` used in a lot of places
|
||||||
|
- :class:`.Completion` for completions
|
||||||
|
- :class:`.BaseSignature` as a base class for signatures
|
||||||
|
- :class:`.Signature` for :meth:`.Script.get_signatures` only
|
||||||
|
- :class:`.ParamName` used for parameters of signatures
|
||||||
|
- :class:`.Refactoring` for refactorings
|
||||||
|
- :class:`.SyntaxError` for :meth:`.Script.get_syntax_errors` only
|
||||||
|
|
||||||
|
These classes are the much biggest part of the API, because they contain
|
||||||
|
the interesting information about all operations.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import sys
|
from pathlib import Path
|
||||||
import warnings
|
from typing import Optional
|
||||||
|
|
||||||
from parso.python.tree import search_ancestor
|
from parso.tree import search_ancestor
|
||||||
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.utils import unite
|
from jedi.inference.utils import unite
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.inference import imports
|
from jedi.inference.compiled.mixed import MixedName
|
||||||
from jedi.inference.imports import ImportName
|
from jedi.inference.names import ImportName, SubModuleName
|
||||||
from jedi.inference.gradual.typeshed import StubModuleValue
|
from jedi.inference.gradual.stub_value import StubModuleValue
|
||||||
from jedi.inference.gradual.conversion import convert_names, convert_values
|
from jedi.inference.gradual.conversion import convert_names, convert_values
|
||||||
from jedi.inference.base_value import ValueSet
|
from jedi.inference.base_value import ValueSet
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
@@ -32,18 +42,21 @@ def defined_names(inference_state, context):
|
|||||||
List sub-definitions (e.g., methods in class).
|
List sub-definitions (e.g., methods in class).
|
||||||
|
|
||||||
:type scope: Scope
|
:type scope: Scope
|
||||||
:rtype: list of Definition
|
:rtype: list of Name
|
||||||
"""
|
"""
|
||||||
filter = next(context.get_filters())
|
filter = next(context.get_filters())
|
||||||
names = [name for name in filter.values()]
|
names = [name for name in filter.values()]
|
||||||
return [Definition(inference_state, n) for n in _sort_names_by_start_pos(names)]
|
return [Name(inference_state, n) for n in _sort_names_by_start_pos(names)]
|
||||||
|
|
||||||
|
|
||||||
def _values_to_definitions(values):
|
def _values_to_definitions(values):
|
||||||
return [Definition(c.inference_state, c.name) for c in values]
|
return [Name(c.inference_state, c.name) for c in values]
|
||||||
|
|
||||||
|
|
||||||
class BaseDefinition(object):
|
class BaseName:
|
||||||
|
"""
|
||||||
|
The base class for all definitions, completions and signatures.
|
||||||
|
"""
|
||||||
_mapping = {
|
_mapping = {
|
||||||
'posixpath': 'os.path',
|
'posixpath': 'os.path',
|
||||||
'riscospath': 'os.path',
|
'riscospath': 'os.path',
|
||||||
@@ -57,7 +70,6 @@ class BaseDefinition(object):
|
|||||||
'_collections': 'collections',
|
'_collections': 'collections',
|
||||||
'_socket': 'socket',
|
'_socket': 'socket',
|
||||||
'_sqlite3': 'sqlite3',
|
'_sqlite3': 'sqlite3',
|
||||||
'__builtin__': 'builtins',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
|
_tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
|
||||||
@@ -80,13 +92,17 @@ class BaseDefinition(object):
|
|||||||
return self._name.get_root_context()
|
return self._name.get_root_context()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def module_path(self):
|
def module_path(self) -> Optional[Path]:
|
||||||
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``"""
|
"""
|
||||||
|
Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py``
|
||||||
|
"""
|
||||||
module = self._get_module_context()
|
module = self._get_module_context()
|
||||||
if module.is_stub() or not module.is_compiled():
|
if module.is_stub() or not module.is_compiled():
|
||||||
# Compiled modules should not return a module path even if they
|
# Compiled modules should not return a module path even if they
|
||||||
# have one.
|
# have one.
|
||||||
return self._get_module_context().py__file__()
|
path: Optional[Path] = self._get_module_context().py__file__()
|
||||||
|
if path is not None:
|
||||||
|
return path
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -111,7 +127,6 @@ class BaseDefinition(object):
|
|||||||
to Jedi, :meth:`jedi.Script.infer` should return a list of
|
to Jedi, :meth:`jedi.Script.infer` should return a list of
|
||||||
definition for ``sys``, ``f``, ``C`` and ``x``.
|
definition for ``sys``, ``f``, ``C`` and ``x``.
|
||||||
|
|
||||||
>>> from jedi._compatibility import no_unicode_pprint
|
|
||||||
>>> from jedi import Script
|
>>> from jedi import Script
|
||||||
>>> source = '''
|
>>> source = '''
|
||||||
... import keyword
|
... import keyword
|
||||||
@@ -137,15 +152,15 @@ class BaseDefinition(object):
|
|||||||
so that it is easy to relate the result to the source code.
|
so that it is easy to relate the result to the source code.
|
||||||
|
|
||||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||||
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
|
>>> print(defs) # doctest: +NORMALIZE_WHITESPACE
|
||||||
[<Definition full_name='keyword', description='module keyword'>,
|
[<Name full_name='keyword', description='module keyword'>,
|
||||||
<Definition full_name='__main__.C', description='class C'>,
|
<Name full_name='__main__.C', description='class C'>,
|
||||||
<Definition full_name='__main__.D', description='instance D'>,
|
<Name full_name='__main__.D', description='instance D'>,
|
||||||
<Definition full_name='__main__.f', description='def f'>]
|
<Name full_name='__main__.f', description='def f'>]
|
||||||
|
|
||||||
Finally, here is what you can get from :attr:`type`:
|
Finally, here is what you can get from :attr:`type`:
|
||||||
|
|
||||||
>>> defs = [str(d.type) for d in defs] # It's unicode and in Py2 has u before it.
|
>>> defs = [d.type for d in defs]
|
||||||
>>> defs[0]
|
>>> defs[0]
|
||||||
'module'
|
'module'
|
||||||
>>> defs[1]
|
>>> defs[1]
|
||||||
@@ -155,8 +170,8 @@ class BaseDefinition(object):
|
|||||||
>>> defs[3]
|
>>> defs[3]
|
||||||
'function'
|
'function'
|
||||||
|
|
||||||
Valid values for are ``module``, ``class``, ``instance``, ``function``,
|
Valid values for type are ``module``, ``class``, ``instance``, ``function``,
|
||||||
``param``, ``path`` and ``keyword``.
|
``param``, ``path``, ``keyword``, ``property`` and ``statement``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
tree_name = self._name.tree_name
|
tree_name = self._name.tree_name
|
||||||
@@ -168,7 +183,7 @@ class BaseDefinition(object):
|
|||||||
tree_name.is_definition():
|
tree_name.is_definition():
|
||||||
resolve = True
|
resolve = True
|
||||||
|
|
||||||
if isinstance(self._name, imports.SubModuleName) or resolve:
|
if isinstance(self._name, SubModuleName) or resolve:
|
||||||
for value in self._name.infer():
|
for value in self._name.infer():
|
||||||
return value.api_type
|
return value.api_type
|
||||||
return self._name.api_type
|
return self._name.api_type
|
||||||
@@ -176,7 +191,8 @@ class BaseDefinition(object):
|
|||||||
@property
|
@property
|
||||||
def module_name(self):
|
def module_name(self):
|
||||||
"""
|
"""
|
||||||
The module name.
|
The module name, a bit similar to what ``__name__`` is in a random
|
||||||
|
Python module.
|
||||||
|
|
||||||
>>> from jedi import Script
|
>>> from jedi import Script
|
||||||
>>> source = 'import json'
|
>>> source = 'import json'
|
||||||
@@ -188,7 +204,9 @@ class BaseDefinition(object):
|
|||||||
return self._get_module_context().py__name__()
|
return self._get_module_context().py__name__()
|
||||||
|
|
||||||
def in_builtin_module(self):
|
def in_builtin_module(self):
|
||||||
"""Whether this is a builtin module."""
|
"""
|
||||||
|
Returns True, if this is a builtin module.
|
||||||
|
"""
|
||||||
value = self._get_module_context().get_value()
|
value = self._get_module_context().get_value()
|
||||||
if isinstance(value, StubModuleValue):
|
if isinstance(value, StubModuleValue):
|
||||||
return any(v.is_compiled() for v in value.non_stub_value_set)
|
return any(v.is_compiled() for v in value.non_stub_value_set)
|
||||||
@@ -210,6 +228,39 @@ class BaseDefinition(object):
|
|||||||
return None
|
return None
|
||||||
return start_pos[1]
|
return start_pos[1]
|
||||||
|
|
||||||
|
def get_definition_start_position(self):
|
||||||
|
"""
|
||||||
|
The (row, column) of the start of the definition range. Rows start with
|
||||||
|
1, columns start with 0.
|
||||||
|
|
||||||
|
:rtype: Optional[Tuple[int, int]]
|
||||||
|
"""
|
||||||
|
if self._name.tree_name is None:
|
||||||
|
return None
|
||||||
|
definition = self._name.tree_name.get_definition()
|
||||||
|
if definition is None:
|
||||||
|
return self._name.start_pos
|
||||||
|
return definition.start_pos
|
||||||
|
|
||||||
|
def get_definition_end_position(self):
|
||||||
|
"""
|
||||||
|
The (row, column) of the end of the definition range. Rows start with
|
||||||
|
1, columns start with 0.
|
||||||
|
|
||||||
|
:rtype: Optional[Tuple[int, int]]
|
||||||
|
"""
|
||||||
|
if self._name.tree_name is None:
|
||||||
|
return None
|
||||||
|
definition = self._name.tree_name.get_definition()
|
||||||
|
if definition is None:
|
||||||
|
return self._name.tree_name.end_pos
|
||||||
|
if self.type in ("function", "class"):
|
||||||
|
last_leaf = definition.get_last_leaf()
|
||||||
|
if last_leaf.type == "newline":
|
||||||
|
return last_leaf.get_previous_leaf().end_pos
|
||||||
|
return last_leaf.end_pos
|
||||||
|
return definition.end_pos
|
||||||
|
|
||||||
def docstring(self, raw=False, fast=True):
|
def docstring(self, raw=False, fast=True):
|
||||||
r"""
|
r"""
|
||||||
Return a document string for this completion object.
|
Return a document string for this completion object.
|
||||||
@@ -229,8 +280,8 @@ class BaseDefinition(object):
|
|||||||
Document for function f.
|
Document for function f.
|
||||||
|
|
||||||
Notice that useful extra information is added to the actual
|
Notice that useful extra information is added to the actual
|
||||||
docstring. For function, it is signature. If you need
|
docstring, e.g. function signatures are prepended to their docstrings.
|
||||||
actual docstring, use ``raw=True`` instead.
|
If you need the actual docstring, use ``raw=True`` instead.
|
||||||
|
|
||||||
>>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
|
>>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
|
||||||
Document for function f.
|
Document for function f.
|
||||||
@@ -265,12 +316,11 @@ class BaseDefinition(object):
|
|||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""
|
"""
|
||||||
A description of the :class:`.Definition` object, which is heavily used
|
A description of the :class:`.Name` object, which is heavily used
|
||||||
in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
|
in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
>>> from jedi._compatibility import no_unicode_pprint
|
|
||||||
>>> from jedi import Script
|
>>> from jedi import Script
|
||||||
>>> source = '''
|
>>> source = '''
|
||||||
... def f():
|
... def f():
|
||||||
@@ -283,10 +333,10 @@ class BaseDefinition(object):
|
|||||||
>>> script = Script(source) # line is maximum by default
|
>>> script = Script(source) # line is maximum by default
|
||||||
>>> defs = script.infer(column=3)
|
>>> defs = script.infer(column=3)
|
||||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||||
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
|
>>> print(defs) # doctest: +NORMALIZE_WHITESPACE
|
||||||
[<Definition full_name='__main__.f', description='def f'>,
|
[<Name full_name='__main__.f', description='def f'>,
|
||||||
<Definition full_name='__main__.C', description='class C'>]
|
<Name full_name='__main__.C', description='class C'>]
|
||||||
>>> str(defs[0].description) # strip literals in python2
|
>>> str(defs[0].description)
|
||||||
'def f'
|
'def f'
|
||||||
>>> str(defs[1].description)
|
>>> str(defs[1].description)
|
||||||
'class C'
|
'class C'
|
||||||
@@ -341,7 +391,7 @@ class BaseDefinition(object):
|
|||||||
|
|
||||||
names = self._name.get_qualified_names(include_module_names=True)
|
names = self._name.get_qualified_names(include_module_names=True)
|
||||||
if names is None:
|
if names is None:
|
||||||
return names
|
return None
|
||||||
|
|
||||||
names = list(names)
|
names = list(names)
|
||||||
try:
|
try:
|
||||||
@@ -352,26 +402,40 @@ class BaseDefinition(object):
|
|||||||
return '.'.join(names)
|
return '.'.join(names)
|
||||||
|
|
||||||
def is_stub(self):
|
def is_stub(self):
|
||||||
|
"""
|
||||||
|
Returns True if the current name is defined in a stub file.
|
||||||
|
"""
|
||||||
if not self._name.is_value_name:
|
if not self._name.is_value_name:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self._name.get_root_context().is_stub()
|
return self._name.get_root_context().is_stub()
|
||||||
|
|
||||||
def goto(self, **kwargs):
|
def is_side_effect(self):
|
||||||
with debug.increase_indent_cm('goto for %s' % self._name):
|
"""
|
||||||
return self._goto(**kwargs)
|
Checks if a name is defined as ``self.foo = 3``. In case of self, this
|
||||||
|
function would return False, for foo it would return True.
|
||||||
|
"""
|
||||||
|
tree_name = self._name.tree_name
|
||||||
|
if tree_name is None:
|
||||||
|
return False
|
||||||
|
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
|
||||||
|
|
||||||
def goto_assignments(self, **kwargs): # Python 2...
|
@debug.increase_indent_cm('goto on name')
|
||||||
warnings.warn(
|
def goto(self, *, follow_imports=False, follow_builtin_imports=False,
|
||||||
"Deprecated since version 0.16.0. Use .goto.",
|
only_stubs=False, prefer_stubs=False):
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
return self.goto(**kwargs)
|
|
||||||
|
|
||||||
def _goto(self, follow_imports=False, follow_builtin_imports=False,
|
"""
|
||||||
only_stubs=False, prefer_stubs=False):
|
Like :meth:`.Script.goto` (also supports the same params), but does it
|
||||||
|
for the current name. This is typically useful if you are using
|
||||||
|
something like :meth:`.Script.get_names()`.
|
||||||
|
|
||||||
|
:param follow_imports: The goto call will follow imports.
|
||||||
|
:param follow_builtin_imports: If follow_imports is True will try to
|
||||||
|
look up names in builtins (i.e. compiled or extension modules).
|
||||||
|
:param only_stubs: Only return stubs for this goto call.
|
||||||
|
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
|
||||||
|
:rtype: list of :class:`Name`
|
||||||
|
"""
|
||||||
if not self._name.is_value_name:
|
if not self._name.is_value_name:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -383,14 +447,27 @@ class BaseDefinition(object):
|
|||||||
only_stubs=only_stubs,
|
only_stubs=only_stubs,
|
||||||
prefer_stubs=prefer_stubs,
|
prefer_stubs=prefer_stubs,
|
||||||
)
|
)
|
||||||
return [self if n == self._name else Definition(self._inference_state, n)
|
return [self if n == self._name else Name(self._inference_state, n)
|
||||||
for n in names]
|
for n in names]
|
||||||
|
|
||||||
def infer(self, **kwargs): # Python 2...
|
@debug.increase_indent_cm('infer on name')
|
||||||
with debug.increase_indent_cm('infer for %s' % self._name):
|
def infer(self, *, only_stubs=False, prefer_stubs=False):
|
||||||
return self._infer(**kwargs)
|
"""
|
||||||
|
Like :meth:`.Script.infer`, it can be useful to understand which type
|
||||||
|
the current name has.
|
||||||
|
|
||||||
def _infer(self, only_stubs=False, prefer_stubs=False):
|
Return the actual definitions. I strongly recommend not using it for
|
||||||
|
your completions, because it might slow down |jedi|. If you want to
|
||||||
|
read only a few objects (<=20), it might be useful, especially to get
|
||||||
|
the original docstrings. The basic problem of this function is that it
|
||||||
|
follows all results. This means with 1000 completions (e.g. numpy),
|
||||||
|
it's just very, very slow.
|
||||||
|
|
||||||
|
:param only_stubs: Only return stubs for this goto call.
|
||||||
|
:param prefer_stubs: Prefer stubs to Python objects for this type
|
||||||
|
inference call.
|
||||||
|
:rtype: list of :class:`Name`
|
||||||
|
"""
|
||||||
assert not (only_stubs and prefer_stubs)
|
assert not (only_stubs and prefer_stubs)
|
||||||
|
|
||||||
if not self._name.is_value_name:
|
if not self._name.is_value_name:
|
||||||
@@ -406,38 +483,15 @@ class BaseDefinition(object):
|
|||||||
prefer_stubs=prefer_stubs,
|
prefer_stubs=prefer_stubs,
|
||||||
)
|
)
|
||||||
resulting_names = [c.name for c in values]
|
resulting_names = [c.name for c in values]
|
||||||
return [self if n == self._name else Definition(self._inference_state, n)
|
return [self if n == self._name else Name(self._inference_state, n)
|
||||||
for n in resulting_names]
|
for n in resulting_names]
|
||||||
|
|
||||||
@property
|
|
||||||
@memoize_method
|
|
||||||
def params(self):
|
|
||||||
"""
|
|
||||||
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
|
|
||||||
|
|
||||||
Raises an ``AttributeError`` if the definition is not callable.
|
|
||||||
Otherwise returns a list of `Definition` that represents the params.
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"Deprecated since version 0.16.0. Use get_signatures()[...].params",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
# Only return the first one. There might be multiple one, especially
|
|
||||||
# with overloading.
|
|
||||||
for signature in self._get_signatures():
|
|
||||||
return [
|
|
||||||
Definition(self._inference_state, n)
|
|
||||||
for n in signature.get_param_names(resolve_stars=True)
|
|
||||||
]
|
|
||||||
|
|
||||||
if self.type == 'function' or self.type == 'class':
|
|
||||||
# Fallback, if no signatures were defined (which is probably by
|
|
||||||
# itself a bug).
|
|
||||||
return []
|
|
||||||
raise AttributeError('There are no params defined on this.')
|
|
||||||
|
|
||||||
def parent(self):
|
def parent(self):
|
||||||
|
"""
|
||||||
|
Returns the parent scope of this identifier.
|
||||||
|
|
||||||
|
:rtype: Name
|
||||||
|
"""
|
||||||
if not self._name.is_value_name:
|
if not self._name.is_value_name:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -463,7 +517,7 @@ class BaseDefinition(object):
|
|||||||
# Happens for comprehension contexts
|
# Happens for comprehension contexts
|
||||||
context = context.parent_context
|
context = context.parent_context
|
||||||
|
|
||||||
return Definition(self._inference_state, context.name)
|
return Name(self._inference_state, context.name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %sname=%r, description=%r>" % (
|
return "<%s %sname=%r, description=%r>" % (
|
||||||
@@ -496,32 +550,64 @@ class BaseDefinition(object):
|
|||||||
return ''.join(lines[start_index:index + after + 1])
|
return ''.join(lines[start_index:index + after + 1])
|
||||||
|
|
||||||
def _get_signatures(self, for_docstring=False):
|
def _get_signatures(self, for_docstring=False):
|
||||||
|
if self._name.api_type == 'property':
|
||||||
|
return []
|
||||||
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
||||||
# For docstrings we don't resolve signatures if they are simple
|
# For docstrings we don't resolve signatures if they are simple
|
||||||
# statements and not stubs. This is a speed optimization.
|
# statements and not stubs. This is a speed optimization.
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if isinstance(self._name, MixedName):
|
||||||
|
# While this would eventually happen anyway, it's basically just a
|
||||||
|
# shortcut to not infer anything tree related, because it's really
|
||||||
|
# not necessary.
|
||||||
|
return self._name.infer_compiled_value().get_signatures()
|
||||||
|
|
||||||
names = convert_names([self._name], prefer_stubs=True)
|
names = convert_names([self._name], prefer_stubs=True)
|
||||||
return [sig for name in names for sig in name.infer().get_signatures()]
|
return [sig for name in names for sig in name.infer().get_signatures()]
|
||||||
|
|
||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
|
"""
|
||||||
|
Returns all potential signatures for a function or a class. Multiple
|
||||||
|
signatures are typical if you use Python stubs with ``@overload``.
|
||||||
|
|
||||||
|
:rtype: list of :class:`BaseSignature`
|
||||||
|
"""
|
||||||
return [
|
return [
|
||||||
BaseSignature(self._inference_state, s)
|
BaseSignature(self._inference_state, s)
|
||||||
for s in self._get_signatures()
|
for s in self._get_signatures()
|
||||||
]
|
]
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
|
"""
|
||||||
|
Uses type inference to "execute" this identifier and returns the
|
||||||
|
executed objects.
|
||||||
|
|
||||||
|
:rtype: list of :class:`Name`
|
||||||
|
"""
|
||||||
return _values_to_definitions(self._name.infer().execute_with_values())
|
return _values_to_definitions(self._name.infer().execute_with_values())
|
||||||
|
|
||||||
|
def get_type_hint(self):
|
||||||
|
"""
|
||||||
|
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
||||||
|
|
||||||
class Completion(BaseDefinition):
|
This method might be quite slow, especially for functions. The problem
|
||||||
|
is finding executions for those functions to return something like
|
||||||
|
``Callable[[int, str], str]``.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self._name.infer().get_type_hint()
|
||||||
|
|
||||||
|
|
||||||
|
class Completion(BaseName):
|
||||||
"""
|
"""
|
||||||
`Completion` objects are returned from :meth:`api.Script.complete`. They
|
``Completion`` objects are returned from :meth:`.Script.complete`. They
|
||||||
provide additional information about a completion.
|
provide additional information about a completion.
|
||||||
"""
|
"""
|
||||||
def __init__(self, inference_state, name, stack, like_name_length,
|
def __init__(self, inference_state, name, stack, like_name_length,
|
||||||
is_fuzzy, cached_name=None):
|
is_fuzzy, cached_name=None):
|
||||||
super(Completion, self).__init__(inference_state, name)
|
super().__init__(inference_state, name)
|
||||||
|
|
||||||
self._like_name_length = like_name_length
|
self._like_name_length = like_name_length
|
||||||
self._stack = stack
|
self._stack = stack
|
||||||
@@ -554,15 +640,15 @@ class Completion(BaseDefinition):
|
|||||||
isinstan# <-- Cursor is here
|
isinstan# <-- Cursor is here
|
||||||
|
|
||||||
would return the string 'ce'. It also adds additional stuff, depending
|
would return the string 'ce'. It also adds additional stuff, depending
|
||||||
on your `settings.py`.
|
on your ``settings.py``.
|
||||||
|
|
||||||
Assuming the following function definition::
|
Assuming the following function definition::
|
||||||
|
|
||||||
def foo(param=0):
|
def foo(param=0):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
completing ``foo(par`` would give a ``Completion`` which `complete`
|
completing ``foo(par`` would give a ``Completion`` which ``complete``
|
||||||
would be `am=`
|
would be ``am=``.
|
||||||
"""
|
"""
|
||||||
if self._is_fuzzy:
|
if self._is_fuzzy:
|
||||||
return None
|
return None
|
||||||
@@ -571,7 +657,7 @@ class Completion(BaseDefinition):
|
|||||||
@property
|
@property
|
||||||
def name_with_symbols(self):
|
def name_with_symbols(self):
|
||||||
"""
|
"""
|
||||||
Similar to :attr:`name`, but like :attr:`name` returns also the
|
Similar to :attr:`.name`, but like :attr:`.name` returns also the
|
||||||
symbols, for example assuming the following function definition::
|
symbols, for example assuming the following function definition::
|
||||||
|
|
||||||
def foo(param=0):
|
def foo(param=0):
|
||||||
@@ -584,12 +670,15 @@ class Completion(BaseDefinition):
|
|||||||
return self._complete(False)
|
return self._complete(False)
|
||||||
|
|
||||||
def docstring(self, raw=False, fast=True):
|
def docstring(self, raw=False, fast=True):
|
||||||
|
"""
|
||||||
|
Documented under :meth:`BaseName.docstring`.
|
||||||
|
"""
|
||||||
if self._like_name_length >= 3:
|
if self._like_name_length >= 3:
|
||||||
# In this case we can just resolve the like name, because we
|
# In this case we can just resolve the like name, because we
|
||||||
# wouldn't load like > 100 Python modules anymore.
|
# wouldn't load like > 100 Python modules anymore.
|
||||||
fast = False
|
fast = False
|
||||||
|
|
||||||
return super(Completion, self).docstring(raw=raw, fast=fast)
|
return super().docstring(raw=raw, fast=fast)
|
||||||
|
|
||||||
def _get_docstring(self):
|
def _get_docstring(self):
|
||||||
if self._cached_name is not None:
|
if self._cached_name is not None:
|
||||||
@@ -598,7 +687,7 @@ class Completion(BaseDefinition):
|
|||||||
self._name.get_public_name(),
|
self._name.get_public_name(),
|
||||||
lambda: self._get_cache()
|
lambda: self._get_cache()
|
||||||
)
|
)
|
||||||
return super(Completion, self)._get_docstring()
|
return super()._get_docstring()
|
||||||
|
|
||||||
def _get_docstring_signature(self):
|
def _get_docstring_signature(self):
|
||||||
if self._cached_name is not None:
|
if self._cached_name is not None:
|
||||||
@@ -607,18 +696,20 @@ class Completion(BaseDefinition):
|
|||||||
self._name.get_public_name(),
|
self._name.get_public_name(),
|
||||||
lambda: self._get_cache()
|
lambda: self._get_cache()
|
||||||
)
|
)
|
||||||
return super(Completion, self)._get_docstring_signature()
|
return super()._get_docstring_signature()
|
||||||
|
|
||||||
def _get_cache(self):
|
def _get_cache(self):
|
||||||
typ = super(Completion, self).type
|
|
||||||
return (
|
return (
|
||||||
typ,
|
super().type,
|
||||||
super(Completion, self)._get_docstring_signature(),
|
super()._get_docstring_signature(),
|
||||||
super(Completion, self)._get_docstring(),
|
super()._get_docstring(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
|
"""
|
||||||
|
Documented under :meth:`BaseName.type`.
|
||||||
|
"""
|
||||||
# Purely a speed optimization.
|
# Purely a speed optimization.
|
||||||
if self._cached_name is not None:
|
if self._cached_name is not None:
|
||||||
return completion_cache.get_type(
|
return completion_cache.get_type(
|
||||||
@@ -627,59 +718,44 @@ class Completion(BaseDefinition):
|
|||||||
lambda: self._get_cache()
|
lambda: self._get_cache()
|
||||||
)
|
)
|
||||||
|
|
||||||
return super(Completion, self).type
|
return super().type
|
||||||
|
|
||||||
|
def get_completion_prefix_length(self):
|
||||||
|
"""
|
||||||
|
Returns the length of the prefix being completed.
|
||||||
|
For example, completing ``isinstance``::
|
||||||
|
|
||||||
|
isinstan# <-- Cursor is here
|
||||||
|
|
||||||
|
would return 8, because len('isinstan') == 8.
|
||||||
|
|
||||||
|
Assuming the following function definition::
|
||||||
|
|
||||||
|
def foo(param=0):
|
||||||
|
pass
|
||||||
|
|
||||||
|
completing ``foo(par`` would return 3.
|
||||||
|
"""
|
||||||
|
return self._like_name_length
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())
|
return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())
|
||||||
|
|
||||||
@memoize_method
|
|
||||||
def follow_definition(self):
|
|
||||||
"""
|
|
||||||
Deprecated!
|
|
||||||
|
|
||||||
Return the original definitions. I strongly recommend not using it for
|
class Name(BaseName):
|
||||||
your completions, because it might slow down |jedi|. If you want to
|
|
||||||
read only a few objects (<=20), it might be useful, especially to get
|
|
||||||
the original docstrings. The basic problem of this function is that it
|
|
||||||
follows all results. This means with 1000 completions (e.g. numpy),
|
|
||||||
it's just PITA-slow.
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"Deprecated since version 0.14.0. Use .infer.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
return self.infer()
|
|
||||||
|
|
||||||
|
|
||||||
class Definition(BaseDefinition):
|
|
||||||
"""
|
"""
|
||||||
*Definition* objects are returned from :meth:`api.Script.goto`
|
*Name* objects are returned from many different APIs including
|
||||||
or :meth:`api.Script.infer`.
|
:meth:`.Script.goto` or :meth:`.Script.infer`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, inference_state, definition):
|
def __init__(self, inference_state, definition):
|
||||||
super(Definition, self).__init__(inference_state, definition)
|
super().__init__(inference_state, definition)
|
||||||
|
|
||||||
@property
|
|
||||||
def desc_with_module(self):
|
|
||||||
"""
|
|
||||||
In addition to the definition, also return the module.
|
|
||||||
|
|
||||||
.. warning:: Don't use this function yet, its behaviour may change. If
|
|
||||||
you really need it, talk to me.
|
|
||||||
|
|
||||||
.. todo:: Add full path. This function is should return a
|
|
||||||
`module.class.function` path.
|
|
||||||
"""
|
|
||||||
position = '' if self.in_builtin_module else '@%s' % self.line
|
|
||||||
return "%s:%s%s" % (self.module_name, self.description, position)
|
|
||||||
|
|
||||||
@memoize_method
|
@memoize_method
|
||||||
def defined_names(self):
|
def defined_names(self):
|
||||||
"""
|
"""
|
||||||
List sub-definitions (e.g., methods in class).
|
List sub-definitions (e.g., methods in class).
|
||||||
|
|
||||||
:rtype: list of Definition
|
:rtype: list of :class:`Name`
|
||||||
"""
|
"""
|
||||||
defs = self._name.infer()
|
defs = self._name.infer()
|
||||||
return sorted(
|
return sorted(
|
||||||
@@ -710,45 +786,53 @@ class Definition(BaseDefinition):
|
|||||||
return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))
|
return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))
|
||||||
|
|
||||||
|
|
||||||
class BaseSignature(Definition):
|
class BaseSignature(Name):
|
||||||
"""
|
"""
|
||||||
`BaseSignature` objects is the return value of `Script.function_definition`.
|
These signatures are returned by :meth:`BaseName.get_signatures`
|
||||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
calls.
|
||||||
return the `isinstance` function. without `(` it would return nothing.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, inference_state, signature):
|
def __init__(self, inference_state, signature):
|
||||||
super(BaseSignature, self).__init__(inference_state, signature.name)
|
super().__init__(inference_state, signature.name)
|
||||||
self._signature = signature
|
self._signature = signature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def params(self):
|
def params(self):
|
||||||
"""
|
"""
|
||||||
:return list of ParamDefinition:
|
Returns definitions for all parameters that a signature defines.
|
||||||
|
This includes stuff like ``*args`` and ``**kwargs``.
|
||||||
|
|
||||||
|
:rtype: list of :class:`.ParamName`
|
||||||
"""
|
"""
|
||||||
return [ParamDefinition(self._inference_state, n)
|
return [ParamName(self._inference_state, n)
|
||||||
for n in self._signature.get_param_names(resolve_stars=True)]
|
for n in self._signature.get_param_names(resolve_stars=True)]
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
|
"""
|
||||||
|
Returns a text representation of the signature. This could for example
|
||||||
|
look like ``foo(bar, baz: int, **kwargs)``.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
return self._signature.to_string()
|
return self._signature.to_string()
|
||||||
|
|
||||||
|
|
||||||
class Signature(BaseSignature):
|
class Signature(BaseSignature):
|
||||||
"""
|
"""
|
||||||
`Signature` objects is the return value of `Script.get_signatures`.
|
A full signature object is the return value of
|
||||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
:meth:`.Script.get_signatures`.
|
||||||
return the `isinstance` function with its params. Without `(` it would
|
|
||||||
return nothing.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, inference_state, signature, call_details):
|
def __init__(self, inference_state, signature, call_details):
|
||||||
super(Signature, self).__init__(inference_state, signature)
|
super().__init__(inference_state, signature)
|
||||||
self._call_details = call_details
|
self._call_details = call_details
|
||||||
self._signature = signature
|
self._signature = signature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def index(self):
|
def index(self):
|
||||||
"""
|
"""
|
||||||
The Param index of the current call.
|
Returns the param index of the current cursor position.
|
||||||
Returns None if the index cannot be found in the curent call.
|
Returns None if the index cannot be found in the curent call.
|
||||||
|
|
||||||
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
return self._call_details.calculate_index(
|
return self._call_details.calculate_index(
|
||||||
self._signature.get_param_names(resolve_stars=True)
|
self._signature.get_param_names(resolve_stars=True)
|
||||||
@@ -757,8 +841,10 @@ class Signature(BaseSignature):
|
|||||||
@property
|
@property
|
||||||
def bracket_start(self):
|
def bracket_start(self):
|
||||||
"""
|
"""
|
||||||
The line/column of the bracket that is responsible for the last
|
Returns a line/column tuple of the bracket that is responsible for the
|
||||||
function call.
|
last function call. The first line is 1 and the first column 0.
|
||||||
|
|
||||||
|
:rtype: int, int
|
||||||
"""
|
"""
|
||||||
return self._call_details.bracket_leaf.start_pos
|
return self._call_details.bracket_leaf.start_pos
|
||||||
|
|
||||||
@@ -770,35 +856,37 @@ class Signature(BaseSignature):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ParamDefinition(Definition):
|
class ParamName(Name):
|
||||||
def infer_default(self):
|
def infer_default(self):
|
||||||
"""
|
"""
|
||||||
:return list of Definition:
|
Returns default values like the ``1`` of ``def foo(x=1):``.
|
||||||
|
|
||||||
|
:rtype: list of :class:`.Name`
|
||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer_default())
|
return _values_to_definitions(self._name.infer_default())
|
||||||
|
|
||||||
def infer_annotation(self, **kwargs):
|
def infer_annotation(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
:return list of Definition:
|
:param execute_annotation: Default True; If False, values are not
|
||||||
|
executed and classes are returned instead of instances.
|
||||||
:param execute_annotation: If False, the values are not executed and
|
:rtype: list of :class:`.Name`
|
||||||
you get classes instead of instances.
|
|
||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
|
"""
|
||||||
|
Returns a simple representation of a param, like
|
||||||
|
``f: Callable[..., Any]``.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
return self._name.to_string()
|
return self._name.to_string()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def kind(self):
|
def kind(self):
|
||||||
"""
|
"""
|
||||||
Returns an enum instance. Returns the same values as the builtin
|
Returns an enum instance of :mod:`inspect`'s ``Parameter`` enum.
|
||||||
:py:attr:`inspect.Parameter.kind`.
|
|
||||||
|
|
||||||
No support for Python < 3.4 anymore.
|
:rtype: :py:attr:`inspect.Parameter.kind`
|
||||||
"""
|
"""
|
||||||
if sys.version_info < (3, 5):
|
|
||||||
raise NotImplementedError(
|
|
||||||
'Python 2 is end-of-life, the new feature is not available for it'
|
|
||||||
)
|
|
||||||
return self._name.get_kind()
|
return self._name.get_kind()
|
||||||
+209
-121
@@ -1,12 +1,12 @@
|
|||||||
import re
|
import re
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
from inspect import Parameter
|
||||||
|
|
||||||
from parso.python.token import PythonTokenTypes
|
from parso.python.token import PythonTokenTypes
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
from parso.tree import search_ancestor, Leaf
|
from parso.tree import search_ancestor, Leaf
|
||||||
from parso import split_lines
|
from parso import split_lines
|
||||||
|
|
||||||
from jedi._compatibility import Parameter
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.api import classes
|
from jedi.api import classes
|
||||||
@@ -19,8 +19,8 @@ from jedi.inference.base_value import ValueSet
|
|||||||
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
|
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
|
||||||
from jedi.inference.context import get_global_filters
|
from jedi.inference.context import get_global_filters
|
||||||
from jedi.inference.value import TreeInstance, ModuleValue
|
from jedi.inference.value import TreeInstance, ModuleValue
|
||||||
from jedi.inference.names import ParamNameWrapper
|
from jedi.inference.names import ParamNameWrapper, SubModuleName
|
||||||
from jedi.inference.gradual.conversion import convert_values
|
from jedi.inference.gradual.conversion import convert_values, convert_names
|
||||||
from jedi.parser_utils import cut_value_at_position
|
from jedi.parser_utils import cut_value_at_position
|
||||||
from jedi.plugins import plugin_manager
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
@@ -30,16 +30,40 @@ class ParamNameWithEquals(ParamNameWrapper):
|
|||||||
return self.string_name + '='
|
return self.string_name + '='
|
||||||
|
|
||||||
|
|
||||||
def get_signature_param_names(signatures):
|
def _get_signature_param_names(signatures, positional_count, used_kwargs):
|
||||||
# add named params
|
# Add named params
|
||||||
for call_sig in signatures:
|
for call_sig in signatures:
|
||||||
for p in call_sig.params:
|
for i, p in enumerate(call_sig.params):
|
||||||
# Allow protected access, because it's a public API.
|
kind = p.kind
|
||||||
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD,
|
if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
|
||||||
Parameter.KEYWORD_ONLY):
|
continue
|
||||||
|
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
|
||||||
|
and p.name not in used_kwargs:
|
||||||
yield ParamNameWithEquals(p._name)
|
yield ParamNameWithEquals(p._name)
|
||||||
|
|
||||||
|
|
||||||
|
def _must_be_kwarg(signatures, positional_count, used_kwargs):
|
||||||
|
if used_kwargs:
|
||||||
|
return True
|
||||||
|
|
||||||
|
must_be_kwarg = True
|
||||||
|
for signature in signatures:
|
||||||
|
for i, p in enumerate(signature.params):
|
||||||
|
kind = p.kind
|
||||||
|
if kind is Parameter.VAR_POSITIONAL:
|
||||||
|
# In case there were not already kwargs, the next param can
|
||||||
|
# always be a normal argument.
|
||||||
|
return False
|
||||||
|
|
||||||
|
if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
|
||||||
|
Parameter.POSITIONAL_ONLY):
|
||||||
|
must_be_kwarg = False
|
||||||
|
break
|
||||||
|
if not must_be_kwarg:
|
||||||
|
break
|
||||||
|
return must_be_kwarg
|
||||||
|
|
||||||
|
|
||||||
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
|
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
|
||||||
comp_dct = set()
|
comp_dct = set()
|
||||||
if settings.case_insensitive_completion:
|
if settings.case_insensitive_completion:
|
||||||
@@ -48,11 +72,7 @@ def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cac
|
|||||||
string = name.string_name
|
string = name.string_name
|
||||||
if settings.case_insensitive_completion:
|
if settings.case_insensitive_completion:
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
if fuzzy:
|
if helpers.match(string, like_name, fuzzy=fuzzy):
|
||||||
match = helpers.fuzzy_match(string, like_name)
|
|
||||||
else:
|
|
||||||
match = helpers.start_match(string, like_name)
|
|
||||||
if match:
|
|
||||||
new = classes.Completion(
|
new = classes.Completion(
|
||||||
inference_state,
|
inference_state,
|
||||||
name,
|
name,
|
||||||
@@ -135,7 +155,7 @@ class Completion:
|
|||||||
|
|
||||||
if string is not None and not prefixed_completions:
|
if string is not None and not prefixed_completions:
|
||||||
prefixed_completions = list(complete_file_name(
|
prefixed_completions = list(complete_file_name(
|
||||||
self._inference_state, self._module_context, start_leaf, string,
|
self._inference_state, self._module_context, start_leaf, quote, string,
|
||||||
self._like_name, self._signatures_callback,
|
self._like_name, self._signatures_callback,
|
||||||
self._code_lines, self._original_position,
|
self._code_lines, self._original_position,
|
||||||
self._fuzzy
|
self._fuzzy
|
||||||
@@ -233,14 +253,8 @@ class Completion:
|
|||||||
allowed_transitions.append('else')
|
allowed_transitions.append('else')
|
||||||
|
|
||||||
completion_names = []
|
completion_names = []
|
||||||
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
|
|
||||||
|
|
||||||
completion_names += self._complete_keywords(
|
|
||||||
allowed_transitions,
|
|
||||||
only_values=not (not current_line or current_line[-1] in ' \t.;'
|
|
||||||
and current_line[-3:] != '...')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
kwargs_only = False
|
||||||
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
|
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
|
||||||
PythonTokenTypes.INDENT)):
|
PythonTokenTypes.INDENT)):
|
||||||
# This means that we actually have to do type inference.
|
# This means that we actually have to do type inference.
|
||||||
@@ -268,20 +282,41 @@ class Completion:
|
|||||||
elif self._is_parameter_completion():
|
elif self._is_parameter_completion():
|
||||||
completion_names += self._complete_params(leaf)
|
completion_names += self._complete_params(leaf)
|
||||||
else:
|
else:
|
||||||
completion_names += self._complete_global_scope()
|
# Apparently this looks like it's good enough to filter most cases
|
||||||
completion_names += self._complete_inherited(is_function=False)
|
# so that signature completions don't randomly appear.
|
||||||
|
# To understand why this works, three things are important:
|
||||||
|
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
||||||
|
# 2. If there's no `,`, it's at the start and only signatures start
|
||||||
|
# with `(`. Other trailers could start with `.` or `[`.
|
||||||
|
# 3. Decorators are very primitive and have an optional `(` with
|
||||||
|
# optional arglist in them.
|
||||||
|
if nodes[-1] in ['(', ','] \
|
||||||
|
and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
||||||
|
signatures = self._signatures_callback(*self._position)
|
||||||
|
if signatures:
|
||||||
|
call_details = signatures[0]._call_details
|
||||||
|
used_kwargs = list(call_details.iter_used_keyword_arguments())
|
||||||
|
positional_count = call_details.count_positional_arguments()
|
||||||
|
|
||||||
# Apparently this looks like it's good enough to filter most cases
|
completion_names += _get_signature_param_names(
|
||||||
# so that signature completions don't randomly appear.
|
signatures,
|
||||||
# To understand why this works, three things are important:
|
positional_count,
|
||||||
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
used_kwargs,
|
||||||
# 2. If there's no `,`, it's at the start and only signatures start
|
)
|
||||||
# with `(`. Other trailers could start with `.` or `[`.
|
|
||||||
# 3. Decorators are very primitive and have an optional `(` with
|
kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
|
||||||
# optional arglist in them.
|
|
||||||
if nodes[-1] in ['(', ','] and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
if not kwargs_only:
|
||||||
signatures = self._signatures_callback(*self._position)
|
completion_names += self._complete_global_scope()
|
||||||
completion_names += get_signature_param_names(signatures)
|
completion_names += self._complete_inherited(is_function=False)
|
||||||
|
|
||||||
|
if not kwargs_only:
|
||||||
|
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
|
||||||
|
completion_names += self._complete_keywords(
|
||||||
|
allowed_transitions,
|
||||||
|
only_values=not (not current_line or current_line[-1] in ' \t.;'
|
||||||
|
and current_line[-3:] != '...')
|
||||||
|
)
|
||||||
|
|
||||||
return cached_name, completion_names
|
return cached_name, completion_names
|
||||||
|
|
||||||
@@ -305,19 +340,20 @@ class Completion:
|
|||||||
if stack_node.nonterminal == 'funcdef':
|
if stack_node.nonterminal == 'funcdef':
|
||||||
context = get_user_context(self._module_context, self._position)
|
context = get_user_context(self._module_context, self._position)
|
||||||
node = search_ancestor(leaf, 'error_node', 'funcdef')
|
node = search_ancestor(leaf, 'error_node', 'funcdef')
|
||||||
if node.type == 'error_node':
|
if node is not None:
|
||||||
n = node.children[0]
|
if node.type == 'error_node':
|
||||||
if n.type == 'decorators':
|
n = node.children[0]
|
||||||
decorators = n.children
|
if n.type == 'decorators':
|
||||||
elif n.type == 'decorator':
|
decorators = n.children
|
||||||
decorators = [n]
|
elif n.type == 'decorator':
|
||||||
|
decorators = [n]
|
||||||
|
else:
|
||||||
|
decorators = []
|
||||||
else:
|
else:
|
||||||
decorators = []
|
decorators = node.get_decorators()
|
||||||
else:
|
function_name = stack_node.nodes[1]
|
||||||
decorators = node.get_decorators()
|
|
||||||
function_name = stack_node.nodes[1]
|
|
||||||
|
|
||||||
return complete_param_names(context, function_name.value, decorators)
|
return complete_param_names(context, function_name.value, decorators)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _complete_keywords(self, allowed_transitions, only_values):
|
def _complete_keywords(self, allowed_transitions, only_values):
|
||||||
@@ -361,80 +397,7 @@ class Completion:
|
|||||||
def _complete_trailer_for_values(self, values):
|
def _complete_trailer_for_values(self, values):
|
||||||
user_context = get_user_context(self._module_context, self._position)
|
user_context = get_user_context(self._module_context, self._position)
|
||||||
|
|
||||||
completion_names = []
|
return complete_trailer(user_context, values)
|
||||||
for value in values:
|
|
||||||
for filter in value.get_filters(origin_scope=user_context.tree_node):
|
|
||||||
completion_names += filter.values()
|
|
||||||
|
|
||||||
if not value.is_stub() and isinstance(value, TreeInstance):
|
|
||||||
completion_names += self._complete_getattr(value)
|
|
||||||
|
|
||||||
python_values = convert_values(values)
|
|
||||||
for c in python_values:
|
|
||||||
if c not in values:
|
|
||||||
for filter in c.get_filters(origin_scope=user_context.tree_node):
|
|
||||||
completion_names += filter.values()
|
|
||||||
return completion_names
|
|
||||||
|
|
||||||
def _complete_getattr(self, instance):
|
|
||||||
"""
|
|
||||||
A heuristic to make completion for proxy objects work. This is not
|
|
||||||
intended to work in all cases. It works exactly in this case:
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
...
|
|
||||||
return getattr(any_object, name)
|
|
||||||
|
|
||||||
It is important that the return contains getattr directly, otherwise it
|
|
||||||
won't work anymore. It's really just a stupid heuristic. It will not
|
|
||||||
work if you write e.g. `return (getatr(o, name))`, because of the
|
|
||||||
additional parentheses. It will also not work if you move the getattr
|
|
||||||
to some other place that is not the return statement itself.
|
|
||||||
|
|
||||||
It is intentional that it doesn't work in all cases. Generally it's
|
|
||||||
really hard to do even this case (as you can see below). Most people
|
|
||||||
will write it like this anyway and the other ones, well they are just
|
|
||||||
out of luck I guess :) ~dave.
|
|
||||||
"""
|
|
||||||
names = (instance.get_function_slot_names(u'__getattr__')
|
|
||||||
or instance.get_function_slot_names(u'__getattribute__'))
|
|
||||||
functions = ValueSet.from_sets(
|
|
||||||
name.infer()
|
|
||||||
for name in names
|
|
||||||
)
|
|
||||||
for func in functions:
|
|
||||||
tree_node = func.tree_node
|
|
||||||
for return_stmt in tree_node.iter_return_stmts():
|
|
||||||
# Basically until the next comment we just try to find out if a
|
|
||||||
# return statement looks exactly like `return getattr(x, name)`.
|
|
||||||
if return_stmt.type != 'return_stmt':
|
|
||||||
continue
|
|
||||||
atom_expr = return_stmt.children[1]
|
|
||||||
if atom_expr.type != 'atom_expr':
|
|
||||||
continue
|
|
||||||
atom = atom_expr.children[0]
|
|
||||||
trailer = atom_expr.children[1]
|
|
||||||
if len(atom_expr.children) != 2 or atom.type != 'name' \
|
|
||||||
or atom.value != 'getattr':
|
|
||||||
continue
|
|
||||||
arglist = trailer.children[1]
|
|
||||||
if arglist.type != 'arglist' or len(arglist.children) < 3:
|
|
||||||
continue
|
|
||||||
context = func.as_context()
|
|
||||||
object_node = arglist.children[0]
|
|
||||||
|
|
||||||
# Make sure it's a param: foo in __getattr__(self, foo)
|
|
||||||
name_node = arglist.children[2]
|
|
||||||
name_list = context.goto(name_node, name_node.start_pos)
|
|
||||||
if not any(n.api_type == 'param' for n in name_list):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Now that we know that these are most probably completion
|
|
||||||
# objects, we just infer the object and return them as
|
|
||||||
# completions.
|
|
||||||
objects = context.infer_node(object_node)
|
|
||||||
return self._complete_trailer_for_values(objects)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _get_importer_names(self, names, level=0, only_modules=True):
|
def _get_importer_names(self, names, level=0, only_modules=True):
|
||||||
names = [n.value for n in names]
|
names = [n.value for n in names]
|
||||||
@@ -490,7 +453,7 @@ class Completion:
|
|||||||
relevant_code_lines = list(iter_relevant_lines(code_lines))
|
relevant_code_lines = list(iter_relevant_lines(code_lines))
|
||||||
if relevant_code_lines[-1] is not None:
|
if relevant_code_lines[-1] is not None:
|
||||||
# Some code lines might be None, therefore get rid of that.
|
# Some code lines might be None, therefore get rid of that.
|
||||||
relevant_code_lines = [c or '\n' for c in relevant_code_lines]
|
relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines]
|
||||||
return self._complete_code_lines(relevant_code_lines)
|
return self._complete_code_lines(relevant_code_lines)
|
||||||
match = re.search(r'`([^`\s]+)', code_lines[-1])
|
match = re.search(r'`([^`\s]+)', code_lines[-1])
|
||||||
if match:
|
if match:
|
||||||
@@ -534,6 +497,8 @@ def _extract_string_while_in_string(leaf, position):
|
|||||||
if leaf.line == position[0]:
|
if leaf.line == position[0]:
|
||||||
kwargs['endpos'] = position[1] - leaf.column
|
kwargs['endpos'] = position[1] - leaf.column
|
||||||
match = _string_start.match(leaf.value, **kwargs)
|
match = _string_start.match(leaf.value, **kwargs)
|
||||||
|
if not match:
|
||||||
|
return None, None, None
|
||||||
start = match.group(0)
|
start = match.group(0)
|
||||||
if leaf.line == position[0] and position[1] < leaf.column + match.end():
|
if leaf.line == position[0] and position[1] < leaf.column + match.end():
|
||||||
return None, None, None
|
return None, None, None
|
||||||
@@ -572,3 +537,126 @@ def _extract_string_while_in_string(leaf, position):
|
|||||||
leaves.insert(0, leaf)
|
leaves.insert(0, leaf)
|
||||||
leaf = leaf.get_previous_leaf()
|
leaf = leaf.get_previous_leaf()
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def complete_trailer(user_context, values):
|
||||||
|
completion_names = []
|
||||||
|
for value in values:
|
||||||
|
for filter in value.get_filters(origin_scope=user_context.tree_node):
|
||||||
|
completion_names += filter.values()
|
||||||
|
|
||||||
|
if not value.is_stub() and isinstance(value, TreeInstance):
|
||||||
|
completion_names += _complete_getattr(user_context, value)
|
||||||
|
|
||||||
|
python_values = convert_values(values)
|
||||||
|
for c in python_values:
|
||||||
|
if c not in values:
|
||||||
|
for filter in c.get_filters(origin_scope=user_context.tree_node):
|
||||||
|
completion_names += filter.values()
|
||||||
|
return completion_names
|
||||||
|
|
||||||
|
|
||||||
|
def _complete_getattr(user_context, instance):
|
||||||
|
"""
|
||||||
|
A heuristic to make completion for proxy objects work. This is not
|
||||||
|
intended to work in all cases. It works exactly in this case:
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
...
|
||||||
|
return getattr(any_object, name)
|
||||||
|
|
||||||
|
It is important that the return contains getattr directly, otherwise it
|
||||||
|
won't work anymore. It's really just a stupid heuristic. It will not
|
||||||
|
work if you write e.g. `return (getatr(o, name))`, because of the
|
||||||
|
additional parentheses. It will also not work if you move the getattr
|
||||||
|
to some other place that is not the return statement itself.
|
||||||
|
|
||||||
|
It is intentional that it doesn't work in all cases. Generally it's
|
||||||
|
really hard to do even this case (as you can see below). Most people
|
||||||
|
will write it like this anyway and the other ones, well they are just
|
||||||
|
out of luck I guess :) ~dave.
|
||||||
|
"""
|
||||||
|
names = (instance.get_function_slot_names('__getattr__')
|
||||||
|
or instance.get_function_slot_names('__getattribute__'))
|
||||||
|
functions = ValueSet.from_sets(
|
||||||
|
name.infer()
|
||||||
|
for name in names
|
||||||
|
)
|
||||||
|
for func in functions:
|
||||||
|
tree_node = func.tree_node
|
||||||
|
if tree_node is None or tree_node.type != 'funcdef':
|
||||||
|
continue
|
||||||
|
|
||||||
|
for return_stmt in tree_node.iter_return_stmts():
|
||||||
|
# Basically until the next comment we just try to find out if a
|
||||||
|
# return statement looks exactly like `return getattr(x, name)`.
|
||||||
|
if return_stmt.type != 'return_stmt':
|
||||||
|
continue
|
||||||
|
atom_expr = return_stmt.children[1]
|
||||||
|
if atom_expr.type != 'atom_expr':
|
||||||
|
continue
|
||||||
|
atom = atom_expr.children[0]
|
||||||
|
trailer = atom_expr.children[1]
|
||||||
|
if len(atom_expr.children) != 2 or atom.type != 'name' \
|
||||||
|
or atom.value != 'getattr':
|
||||||
|
continue
|
||||||
|
arglist = trailer.children[1]
|
||||||
|
if arglist.type != 'arglist' or len(arglist.children) < 3:
|
||||||
|
continue
|
||||||
|
context = func.as_context()
|
||||||
|
object_node = arglist.children[0]
|
||||||
|
|
||||||
|
# Make sure it's a param: foo in __getattr__(self, foo)
|
||||||
|
name_node = arglist.children[2]
|
||||||
|
name_list = context.goto(name_node, name_node.start_pos)
|
||||||
|
if not any(n.api_type == 'param' for n in name_list):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Now that we know that these are most probably completion
|
||||||
|
# objects, we just infer the object and return them as
|
||||||
|
# completions.
|
||||||
|
objects = context.infer_node(object_node)
|
||||||
|
return complete_trailer(user_context, objects)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def search_in_module(inference_state, module_context, names, wanted_names,
|
||||||
|
wanted_type, complete=False, fuzzy=False,
|
||||||
|
ignore_imports=False, convert=False):
|
||||||
|
for s in wanted_names[:-1]:
|
||||||
|
new_names = []
|
||||||
|
for n in names:
|
||||||
|
if s == n.string_name:
|
||||||
|
if n.tree_name is not None and n.api_type in ('module', 'namespace') \
|
||||||
|
and ignore_imports:
|
||||||
|
continue
|
||||||
|
new_names += complete_trailer(
|
||||||
|
module_context,
|
||||||
|
n.infer()
|
||||||
|
)
|
||||||
|
debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
|
||||||
|
names = new_names
|
||||||
|
|
||||||
|
last_name = wanted_names[-1].lower()
|
||||||
|
for n in names:
|
||||||
|
string = n.string_name.lower()
|
||||||
|
if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
|
||||||
|
or not complete and string == last_name:
|
||||||
|
if isinstance(n, SubModuleName):
|
||||||
|
names = [v.name for v in n.infer()]
|
||||||
|
else:
|
||||||
|
names = [n]
|
||||||
|
if convert:
|
||||||
|
names = convert_names(names)
|
||||||
|
for n2 in names:
|
||||||
|
if complete:
|
||||||
|
def_ = classes.Completion(
|
||||||
|
inference_state, n2,
|
||||||
|
stack=None,
|
||||||
|
like_name_length=len(last_name),
|
||||||
|
is_fuzzy=fuzzy,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
def_ = classes.Name(inference_state, n2)
|
||||||
|
if not wanted_type or wanted_type == def_.type:
|
||||||
|
yield def_
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
_cache = {}
|
from typing import Dict, Tuple, Callable
|
||||||
|
|
||||||
|
CacheValues = Tuple[str, str, str]
|
||||||
|
CacheValuesCallback = Callable[[], CacheValues]
|
||||||
|
|
||||||
|
|
||||||
def save_entry(module_name, name, cache):
|
_cache: Dict[str, Dict[str, CacheValues]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def save_entry(module_name: str, name: str, cache: CacheValues) -> None:
|
||||||
try:
|
try:
|
||||||
module_cache = _cache[module_name]
|
module_cache = _cache[module_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -9,8 +15,8 @@ def save_entry(module_name, name, cache):
|
|||||||
module_cache[name] = cache
|
module_cache[name] = cache
|
||||||
|
|
||||||
|
|
||||||
def _create_get_from_cache(number):
|
def _create_get_from_cache(number: int) -> Callable[[str, str, CacheValuesCallback], str]:
|
||||||
def _get_from_cache(module_name, name, get_cache_values):
|
def _get_from_cache(module_name: str, name: str, get_cache_values: CacheValuesCallback) -> str:
|
||||||
try:
|
try:
|
||||||
return _cache[module_name][name][number]
|
return _cache[module_name][name][number]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
+65
-71
@@ -7,8 +7,8 @@ import sys
|
|||||||
import hashlib
|
import hashlib
|
||||||
import filecmp
|
import filecmp
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from shutil import which
|
||||||
|
|
||||||
from jedi._compatibility import highest_pickle_protocol, which
|
|
||||||
from jedi.cache import memoize_method, time_cache
|
from jedi.cache import memoize_method, time_cache
|
||||||
from jedi.inference.compiled.subprocess import CompiledSubprocess, \
|
from jedi.inference.compiled.subprocess import CompiledSubprocess, \
|
||||||
InferenceStateSameProcess, InferenceStateSubprocess
|
InferenceStateSameProcess, InferenceStateSubprocess
|
||||||
@@ -17,7 +17,7 @@ import parso
|
|||||||
|
|
||||||
_VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
_VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||||
|
|
||||||
_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '3.4', '2.7']
|
_SUPPORTED_PYTHONS = ['3.9', '3.8', '3.7', '3.6']
|
||||||
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
|
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
|
||||||
_CONDA_VAR = 'CONDA_PREFIX'
|
_CONDA_VAR = 'CONDA_PREFIX'
|
||||||
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
|
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
|
||||||
@@ -30,7 +30,7 @@ class InvalidPythonEnvironment(Exception):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class _BaseEnvironment(object):
|
class _BaseEnvironment:
|
||||||
@memoize_method
|
@memoize_method
|
||||||
def get_grammar(self):
|
def get_grammar(self):
|
||||||
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
|
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
|
||||||
@@ -61,8 +61,9 @@ class Environment(_BaseEnvironment):
|
|||||||
"""
|
"""
|
||||||
_subprocess = None
|
_subprocess = None
|
||||||
|
|
||||||
def __init__(self, executable):
|
def __init__(self, executable, env_vars=None):
|
||||||
self._start_executable = executable
|
self._start_executable = executable
|
||||||
|
self._env_vars = env_vars
|
||||||
# Initialize the environment
|
# Initialize the environment
|
||||||
self._get_subprocess()
|
self._get_subprocess()
|
||||||
|
|
||||||
@@ -71,7 +72,8 @@ class Environment(_BaseEnvironment):
|
|||||||
return self._subprocess
|
return self._subprocess
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._subprocess = CompiledSubprocess(self._start_executable)
|
self._subprocess = CompiledSubprocess(self._start_executable,
|
||||||
|
env_vars=self._env_vars)
|
||||||
info = self._subprocess._send(None, _get_info)
|
info = self._subprocess._send(None, _get_info)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise InvalidPythonEnvironment(
|
raise InvalidPythonEnvironment(
|
||||||
@@ -91,19 +93,9 @@ class Environment(_BaseEnvironment):
|
|||||||
"""
|
"""
|
||||||
self.version_info = _VersionInfo(*info[2])
|
self.version_info = _VersionInfo(*info[2])
|
||||||
"""
|
"""
|
||||||
Like ``sys.version_info``. A tuple to show the current Environment's
|
Like :data:`sys.version_info`: a tuple to show the current
|
||||||
Python version.
|
Environment's Python version.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# py2 sends bytes via pickle apparently?!
|
|
||||||
if self.version_info.major == 2:
|
|
||||||
self.executable = self.executable.decode()
|
|
||||||
self.path = self.path.decode()
|
|
||||||
|
|
||||||
# Adjust pickle protocol according to host and client version.
|
|
||||||
self._subprocess._pickle_protocol = highest_pickle_protocol([
|
|
||||||
sys.version_info, self.version_info])
|
|
||||||
|
|
||||||
return self._subprocess
|
return self._subprocess
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -117,7 +109,7 @@ class Environment(_BaseEnvironment):
|
|||||||
def get_sys_path(self):
|
def get_sys_path(self):
|
||||||
"""
|
"""
|
||||||
The sys path for this environment. Does not include potential
|
The sys path for this environment. Does not include potential
|
||||||
modifications like ``sys.path.append``.
|
modifications from e.g. appending to :data:`sys.path`.
|
||||||
|
|
||||||
:returns: list of str
|
:returns: list of str
|
||||||
"""
|
"""
|
||||||
@@ -129,11 +121,12 @@ class Environment(_BaseEnvironment):
|
|||||||
return self._get_subprocess().get_sys_path()
|
return self._get_subprocess().get_sys_path()
|
||||||
|
|
||||||
|
|
||||||
class _SameEnvironmentMixin(object):
|
class _SameEnvironmentMixin:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._start_executable = self.executable = sys.executable
|
self._start_executable = self.executable = sys.executable
|
||||||
self.path = sys.prefix
|
self.path = sys.prefix
|
||||||
self.version_info = _VersionInfo(*sys.version_info[:3])
|
self.version_info = _VersionInfo(*sys.version_info[:3])
|
||||||
|
self._env_vars = None
|
||||||
|
|
||||||
|
|
||||||
class SameEnvironment(_SameEnvironmentMixin, Environment):
|
class SameEnvironment(_SameEnvironmentMixin, Environment):
|
||||||
@@ -185,7 +178,7 @@ def get_default_environment():
|
|||||||
makes it possible to use as many new Python features as possible when using
|
makes it possible to use as many new Python features as possible when using
|
||||||
autocompletion and other functionality.
|
autocompletion and other functionality.
|
||||||
|
|
||||||
:returns: :class:`Environment`
|
:returns: :class:`.Environment`
|
||||||
"""
|
"""
|
||||||
virtual_env = _get_virtual_env_from_var()
|
virtual_env = _get_virtual_env_from_var()
|
||||||
if virtual_env is not None:
|
if virtual_env is not None:
|
||||||
@@ -254,10 +247,17 @@ def get_cached_default_environment():
|
|||||||
|
|
||||||
@time_cache(seconds=10 * 60) # 10 Minutes
|
@time_cache(seconds=10 * 60) # 10 Minutes
|
||||||
def _get_cached_default_environment():
|
def _get_cached_default_environment():
|
||||||
return get_default_environment()
|
try:
|
||||||
|
return get_default_environment()
|
||||||
|
except InvalidPythonEnvironment:
|
||||||
|
# It's possible that `sys.executable` is wrong. Typically happens
|
||||||
|
# when Jedi is used in an executable that embeds Python. For further
|
||||||
|
# information, have a look at:
|
||||||
|
# https://github.com/davidhalter/jedi/issues/1531
|
||||||
|
return InterpreterEnvironment()
|
||||||
|
|
||||||
|
|
||||||
def find_virtualenvs(paths=None, **kwargs):
|
def find_virtualenvs(paths=None, *, safe=True, use_environment_vars=True):
|
||||||
"""
|
"""
|
||||||
:param paths: A list of paths in your file system to be scanned for
|
:param paths: A list of paths in your file system to be scanned for
|
||||||
Virtualenvs. It will search in these paths and potentially execute the
|
Virtualenvs. It will search in these paths and potentially execute the
|
||||||
@@ -272,49 +272,46 @@ def find_virtualenvs(paths=None, **kwargs):
|
|||||||
CONDA_PREFIX will be checked to see if it contains a valid conda
|
CONDA_PREFIX will be checked to see if it contains a valid conda
|
||||||
environment.
|
environment.
|
||||||
|
|
||||||
:yields: :class:`Environment`
|
:yields: :class:`.Environment`
|
||||||
"""
|
"""
|
||||||
def py27_comp(paths=None, safe=True, use_environment_vars=True):
|
if paths is None:
|
||||||
if paths is None:
|
paths = []
|
||||||
paths = []
|
|
||||||
|
|
||||||
_used_paths = set()
|
_used_paths = set()
|
||||||
|
|
||||||
if use_environment_vars:
|
if use_environment_vars:
|
||||||
# Using this variable should be safe, because attackers might be
|
# Using this variable should be safe, because attackers might be
|
||||||
# able to drop files (via git) but not environment variables.
|
# able to drop files (via git) but not environment variables.
|
||||||
virtual_env = _get_virtual_env_from_var()
|
virtual_env = _get_virtual_env_from_var()
|
||||||
if virtual_env is not None:
|
if virtual_env is not None:
|
||||||
yield virtual_env
|
yield virtual_env
|
||||||
_used_paths.add(virtual_env.path)
|
_used_paths.add(virtual_env.path)
|
||||||
|
|
||||||
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
|
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
|
||||||
if conda_env is not None:
|
if conda_env is not None:
|
||||||
yield conda_env
|
yield conda_env
|
||||||
_used_paths.add(conda_env.path)
|
_used_paths.add(conda_env.path)
|
||||||
|
|
||||||
for directory in paths:
|
for directory in paths:
|
||||||
if not os.path.isdir(directory):
|
if not os.path.isdir(directory):
|
||||||
|
continue
|
||||||
|
|
||||||
|
directory = os.path.abspath(directory)
|
||||||
|
for path in os.listdir(directory):
|
||||||
|
path = os.path.join(directory, path)
|
||||||
|
if path in _used_paths:
|
||||||
|
# A path shouldn't be inferred twice.
|
||||||
continue
|
continue
|
||||||
|
_used_paths.add(path)
|
||||||
|
|
||||||
directory = os.path.abspath(directory)
|
try:
|
||||||
for path in os.listdir(directory):
|
executable = _get_executable_path(path, safe=safe)
|
||||||
path = os.path.join(directory, path)
|
yield Environment(executable)
|
||||||
if path in _used_paths:
|
except InvalidPythonEnvironment:
|
||||||
# A path shouldn't be inferred twice.
|
pass
|
||||||
continue
|
|
||||||
_used_paths.add(path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
executable = _get_executable_path(path, safe=safe)
|
|
||||||
yield Environment(executable)
|
|
||||||
except InvalidPythonEnvironment:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return py27_comp(paths, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def find_system_environments():
|
def find_system_environments(*, env_vars=None):
|
||||||
"""
|
"""
|
||||||
Ignores virtualenvs and returns the Python versions that were installed on
|
Ignores virtualenvs and returns the Python versions that were installed on
|
||||||
your system. This might return nothing, if you're running Python e.g. from
|
your system. This might return nothing, if you're running Python e.g. from
|
||||||
@@ -322,24 +319,24 @@ def find_system_environments():
|
|||||||
|
|
||||||
The environments are sorted from latest to oldest Python version.
|
The environments are sorted from latest to oldest Python version.
|
||||||
|
|
||||||
:yields: :class:`Environment`
|
:yields: :class:`.Environment`
|
||||||
"""
|
"""
|
||||||
for version_string in _SUPPORTED_PYTHONS:
|
for version_string in _SUPPORTED_PYTHONS:
|
||||||
try:
|
try:
|
||||||
yield get_system_environment(version_string)
|
yield get_system_environment(version_string, env_vars=env_vars)
|
||||||
except InvalidPythonEnvironment:
|
except InvalidPythonEnvironment:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# TODO: this function should probably return a list of environments since
|
# TODO: this function should probably return a list of environments since
|
||||||
# multiple Python installations can be found on a system for the same version.
|
# multiple Python installations can be found on a system for the same version.
|
||||||
def get_system_environment(version):
|
def get_system_environment(version, *, env_vars=None):
|
||||||
"""
|
"""
|
||||||
Return the first Python environment found for a string of the form 'X.Y'
|
Return the first Python environment found for a string of the form 'X.Y'
|
||||||
where X and Y are the major and minor versions of Python.
|
where X and Y are the major and minor versions of Python.
|
||||||
|
|
||||||
:raises: :exc:`.InvalidPythonEnvironment`
|
:raises: :exc:`.InvalidPythonEnvironment`
|
||||||
:returns: :class:`Environment`
|
:returns: :class:`.Environment`
|
||||||
"""
|
"""
|
||||||
exe = which('python' + version)
|
exe = which('python' + version)
|
||||||
if exe:
|
if exe:
|
||||||
@@ -350,24 +347,24 @@ def get_system_environment(version):
|
|||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
for exe in _get_executables_from_windows_registry(version):
|
for exe in _get_executables_from_windows_registry(version):
|
||||||
try:
|
try:
|
||||||
return Environment(exe)
|
return Environment(exe, env_vars=env_vars)
|
||||||
except InvalidPythonEnvironment:
|
except InvalidPythonEnvironment:
|
||||||
pass
|
pass
|
||||||
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
|
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
|
||||||
|
|
||||||
|
|
||||||
def create_environment(path, safe=True):
|
def create_environment(path, *, safe=True, env_vars=None):
|
||||||
"""
|
"""
|
||||||
Make it possible to manually create an Environment object by specifying a
|
Make it possible to manually create an Environment object by specifying a
|
||||||
Virtualenv path or an executable path.
|
Virtualenv path or an executable path and optional environment variables.
|
||||||
|
|
||||||
:raises: :exc:`.InvalidPythonEnvironment`
|
:raises: :exc:`.InvalidPythonEnvironment`
|
||||||
:returns: :class:`Environment`
|
:returns: :class:`.Environment`
|
||||||
"""
|
"""
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
_assert_safe(path, safe)
|
_assert_safe(path, safe)
|
||||||
return Environment(path)
|
return Environment(path, env_vars=env_vars)
|
||||||
return Environment(_get_executable_path(path, safe=safe))
|
return Environment(_get_executable_path(path, safe=safe), env_vars=env_vars)
|
||||||
|
|
||||||
|
|
||||||
def _get_executable_path(path, safe=True):
|
def _get_executable_path(path, safe=True):
|
||||||
@@ -387,11 +384,8 @@ def _get_executable_path(path, safe=True):
|
|||||||
|
|
||||||
|
|
||||||
def _get_executables_from_windows_registry(version):
|
def _get_executables_from_windows_registry(version):
|
||||||
# The winreg module is named _winreg on Python 2.
|
# https://github.com/python/typeshed/pull/3794 adds winreg
|
||||||
try:
|
import winreg # type: ignore[import]
|
||||||
import winreg
|
|
||||||
except ImportError:
|
|
||||||
import _winreg as winreg
|
|
||||||
|
|
||||||
# TODO: support Python Anaconda.
|
# TODO: support Python Anaconda.
|
||||||
sub_keys = [
|
sub_keys = [
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
This file is about errors in Python files and not about exception handling in
|
||||||
|
Jedi.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def parso_to_jedi_errors(grammar, module_node):
|
||||||
|
return [SyntaxError(e) for e in grammar.iter_errors(module_node)]
|
||||||
|
|
||||||
|
|
||||||
|
class SyntaxError:
|
||||||
|
"""
|
||||||
|
Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
|
||||||
|
"""
|
||||||
|
def __init__(self, parso_error):
|
||||||
|
self._parso_error = parso_error
|
||||||
|
|
||||||
|
@property
|
||||||
|
def line(self):
|
||||||
|
"""The line where the error starts (starting with 1)."""
|
||||||
|
return self._parso_error.start_pos[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def column(self):
|
||||||
|
"""The column where the error starts (starting with 0)."""
|
||||||
|
return self._parso_error.start_pos[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def until_line(self):
|
||||||
|
"""The line where the error ends (starting with 1)."""
|
||||||
|
return self._parso_error.end_pos[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def until_column(self):
|
||||||
|
"""The column where the error ends (starting with 0)."""
|
||||||
|
return self._parso_error.end_pos[1]
|
||||||
|
|
||||||
|
def get_message(self):
|
||||||
|
return self._parso_error.message
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s from=%s to=%s>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self._parso_error.start_pos,
|
||||||
|
self._parso_error.end_pos,
|
||||||
|
)
|
||||||
+23
-2
@@ -3,8 +3,29 @@ class _JediError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class InternalError(_JediError):
|
class InternalError(_JediError):
|
||||||
pass
|
"""
|
||||||
|
This error might happen a subprocess is crashing. The reason for this is
|
||||||
|
usually broken C code in third party libraries. This is not a very common
|
||||||
|
thing and it is safe to use Jedi again. However using the same calls might
|
||||||
|
result in the same error again.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class WrongVersion(_JediError):
|
class WrongVersion(_JediError):
|
||||||
pass
|
"""
|
||||||
|
This error is reserved for the future, shouldn't really be happening at the
|
||||||
|
moment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class RefactoringError(_JediError):
|
||||||
|
"""
|
||||||
|
Refactorings can fail for various reasons. So if you work with refactorings
|
||||||
|
like :meth:`.Script.rename`, :meth:`.Script.inline`,
|
||||||
|
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
|
||||||
|
sure to catch these. The descriptions in the errors are ususally valuable
|
||||||
|
for end users.
|
||||||
|
|
||||||
|
A typical ``RefactoringError`` would tell the user that inlining is not
|
||||||
|
possible if no name is under the cursor.
|
||||||
|
"""
|
||||||
+11
-13
@@ -1,22 +1,23 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from jedi._compatibility import FileNotFoundError, force_unicode, scandir
|
|
||||||
from jedi.api import classes
|
from jedi.api import classes
|
||||||
from jedi.api.strings import StringName, get_quote_ending
|
from jedi.api.strings import StringName, get_quote_ending
|
||||||
from jedi.api.helpers import fuzzy_match, start_match
|
from jedi.api.helpers import match
|
||||||
from jedi.inference.helpers import get_str_or_none
|
from jedi.inference.helpers import get_str_or_none
|
||||||
|
|
||||||
|
|
||||||
class PathName(StringName):
|
class PathName(StringName):
|
||||||
api_type = u'path'
|
api_type = 'path'
|
||||||
|
|
||||||
|
|
||||||
def complete_file_name(inference_state, module_context, start_leaf, string,
|
def complete_file_name(inference_state, module_context, start_leaf, quote, string,
|
||||||
like_name, signatures_callback, code_lines, position, fuzzy):
|
like_name, signatures_callback, code_lines, position, fuzzy):
|
||||||
# First we want to find out what can actually be changed as a name.
|
# First we want to find out what can actually be changed as a name.
|
||||||
like_name_length = len(os.path.basename(string))
|
like_name_length = len(os.path.basename(string))
|
||||||
|
|
||||||
addition = _get_string_additions(module_context, start_leaf)
|
addition = _get_string_additions(module_context, start_leaf)
|
||||||
|
if string.startswith('~'):
|
||||||
|
string = os.path.expanduser(string)
|
||||||
if addition is None:
|
if addition is None:
|
||||||
return
|
return
|
||||||
string = addition + string
|
string = addition + string
|
||||||
@@ -34,21 +35,18 @@ def complete_file_name(inference_state, module_context, start_leaf, string,
|
|||||||
is_in_os_path_join = False
|
is_in_os_path_join = False
|
||||||
else:
|
else:
|
||||||
string = to_be_added + string
|
string = to_be_added + string
|
||||||
base_path = os.path.join(inference_state.project._path, string)
|
base_path = os.path.join(inference_state.project.path, string)
|
||||||
try:
|
try:
|
||||||
listed = sorted(scandir(base_path), key=lambda e: e.name)
|
listed = sorted(os.scandir(base_path), key=lambda e: e.name)
|
||||||
# OSError: [Errno 36] File name too long: '...'
|
# OSError: [Errno 36] File name too long: '...'
|
||||||
except (FileNotFoundError, OSError):
|
except (FileNotFoundError, OSError):
|
||||||
return
|
return
|
||||||
|
quote_ending = get_quote_ending(quote, code_lines, position)
|
||||||
for entry in listed:
|
for entry in listed:
|
||||||
name = entry.name
|
name = entry.name
|
||||||
if fuzzy:
|
if match(name, must_start_with, fuzzy=fuzzy):
|
||||||
match = fuzzy_match(name, must_start_with)
|
|
||||||
else:
|
|
||||||
match = start_match(name, must_start_with)
|
|
||||||
if match:
|
|
||||||
if is_in_os_path_join or not entry.is_dir():
|
if is_in_os_path_join or not entry.is_dir():
|
||||||
name += get_quote_ending(start_leaf.value, code_lines, position)
|
name += quote_ending
|
||||||
else:
|
else:
|
||||||
name += os.path.sep
|
name += os.path.sep
|
||||||
|
|
||||||
@@ -95,7 +93,7 @@ def _add_strings(context, nodes, add_slash=False):
|
|||||||
return None
|
return None
|
||||||
if not first and add_slash:
|
if not first and add_slash:
|
||||||
string += os.path.sep
|
string += os.path.sep
|
||||||
string += force_unicode(s)
|
string += s
|
||||||
first = False
|
first = False
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|||||||
+88
-22
@@ -4,38 +4,50 @@ Helpers for the API
|
|||||||
import re
|
import re
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
from itertools import chain
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from inspect import Parameter
|
||||||
|
|
||||||
from parso.python.parser import Parser
|
from parso.python.parser import Parser
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
|
|
||||||
from jedi._compatibility import u, Parameter
|
|
||||||
from jedi.inference.base_value import NO_VALUES
|
from jedi.inference.base_value import NO_VALUES
|
||||||
from jedi.inference.syntax_tree import infer_atom
|
from jedi.inference.syntax_tree import infer_atom
|
||||||
from jedi.inference.helpers import infer_call_of_leaf
|
from jedi.inference.helpers import infer_call_of_leaf
|
||||||
from jedi.inference.compiled import get_string_value_set
|
from jedi.inference.compiled import get_string_value_set
|
||||||
from jedi.cache import signature_time_cache
|
from jedi.cache import signature_time_cache, memoize_method
|
||||||
|
from jedi.parser_utils import get_parent_scope
|
||||||
|
|
||||||
|
|
||||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||||
|
|
||||||
|
|
||||||
def start_match(string, like_name):
|
def _start_match(string, like_name):
|
||||||
return string.startswith(like_name)
|
return string.startswith(like_name)
|
||||||
|
|
||||||
|
|
||||||
def fuzzy_match(string, like_name):
|
def _fuzzy_match(string, like_name):
|
||||||
if len(like_name) <= 1:
|
if len(like_name) <= 1:
|
||||||
return like_name in string
|
return like_name in string
|
||||||
pos = string.find(like_name[0])
|
pos = string.find(like_name[0])
|
||||||
if pos >= 0:
|
if pos >= 0:
|
||||||
return fuzzy_match(string[pos + 1:], like_name[1:])
|
return _fuzzy_match(string[pos + 1:], like_name[1:])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def match(string, like_name, fuzzy=False):
|
||||||
|
if fuzzy:
|
||||||
|
return _fuzzy_match(string, like_name)
|
||||||
|
else:
|
||||||
|
return _start_match(string, like_name)
|
||||||
|
|
||||||
|
|
||||||
def sorted_definitions(defs):
|
def sorted_definitions(defs):
|
||||||
# Note: `or ''` below is required because `module_path` could be
|
# Note: `or ''` below is required because `module_path` could be
|
||||||
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0, x.name))
|
return sorted(defs, key=lambda x: (str(x.module_path or ''),
|
||||||
|
x.line or 0,
|
||||||
|
x.column or 0,
|
||||||
|
x.name))
|
||||||
|
|
||||||
|
|
||||||
def get_on_completion_name(module_node, lines, position):
|
def get_on_completion_name(module_node, lines, position):
|
||||||
@@ -75,18 +87,18 @@ def _get_code_for_stack(code_lines, leaf, position):
|
|||||||
# If we're not on a comment simply get the previous leaf and proceed.
|
# If we're not on a comment simply get the previous leaf and proceed.
|
||||||
leaf = leaf.get_previous_leaf()
|
leaf = leaf.get_previous_leaf()
|
||||||
if leaf is None:
|
if leaf is None:
|
||||||
return u('') # At the beginning of the file.
|
return '' # At the beginning of the file.
|
||||||
|
|
||||||
is_after_newline = leaf.type == 'newline'
|
is_after_newline = leaf.type == 'newline'
|
||||||
while leaf.type == 'newline':
|
while leaf.type == 'newline':
|
||||||
leaf = leaf.get_previous_leaf()
|
leaf = leaf.get_previous_leaf()
|
||||||
if leaf is None:
|
if leaf is None:
|
||||||
return u('')
|
return ''
|
||||||
|
|
||||||
if leaf.type == 'error_leaf' or leaf.type == 'string':
|
if leaf.type == 'error_leaf' or leaf.type == 'string':
|
||||||
if leaf.start_pos[0] < position[0]:
|
if leaf.start_pos[0] < position[0]:
|
||||||
# On a different line, we just begin anew.
|
# On a different line, we just begin anew.
|
||||||
return u('')
|
return ''
|
||||||
|
|
||||||
# Error leafs cannot be parsed, completion in strings is also
|
# Error leafs cannot be parsed, completion in strings is also
|
||||||
# impossible.
|
# impossible.
|
||||||
@@ -102,7 +114,7 @@ def _get_code_for_stack(code_lines, leaf, position):
|
|||||||
if user_stmt.start_pos[1] > position[1]:
|
if user_stmt.start_pos[1] > position[1]:
|
||||||
# This means that it's actually a dedent and that means that we
|
# This means that it's actually a dedent and that means that we
|
||||||
# start without value (part of a suite).
|
# start without value (part of a suite).
|
||||||
return u('')
|
return ''
|
||||||
|
|
||||||
# This is basically getting the relevant lines.
|
# This is basically getting the relevant lines.
|
||||||
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||||
@@ -186,13 +198,12 @@ def filter_follow_imports(names, follow_builtin_imports=False):
|
|||||||
if found_builtin:
|
if found_builtin:
|
||||||
yield name
|
yield name
|
||||||
else:
|
else:
|
||||||
for new_name in new_names:
|
yield from new_names
|
||||||
yield new_name
|
|
||||||
else:
|
else:
|
||||||
yield name
|
yield name
|
||||||
|
|
||||||
|
|
||||||
class CallDetails(object):
|
class CallDetails:
|
||||||
def __init__(self, bracket_leaf, children, position):
|
def __init__(self, bracket_leaf, children, position):
|
||||||
['bracket_leaf', 'call_index', 'keyword_name_str']
|
['bracket_leaf', 'call_index', 'keyword_name_str']
|
||||||
self.bracket_leaf = bracket_leaf
|
self.bracket_leaf = bracket_leaf
|
||||||
@@ -207,11 +218,15 @@ class CallDetails(object):
|
|||||||
def keyword_name_str(self):
|
def keyword_name_str(self):
|
||||||
return _get_index_and_key(self._children, self._position)[1]
|
return _get_index_and_key(self._children, self._position)[1]
|
||||||
|
|
||||||
|
@memoize_method
|
||||||
|
def _list_arguments(self):
|
||||||
|
return list(_iter_arguments(self._children, self._position))
|
||||||
|
|
||||||
def calculate_index(self, param_names):
|
def calculate_index(self, param_names):
|
||||||
positional_count = 0
|
positional_count = 0
|
||||||
used_names = set()
|
used_names = set()
|
||||||
star_count = -1
|
star_count = -1
|
||||||
args = list(_iter_arguments(self._children, self._position))
|
args = self._list_arguments()
|
||||||
if not args:
|
if not args:
|
||||||
if param_names:
|
if param_names:
|
||||||
return 0
|
return 0
|
||||||
@@ -258,6 +273,19 @@ class CallDetails(object):
|
|||||||
return i
|
return i
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def iter_used_keyword_arguments(self):
|
||||||
|
for star_count, key_start, had_equal in list(self._list_arguments()):
|
||||||
|
if had_equal and key_start:
|
||||||
|
yield key_start
|
||||||
|
|
||||||
|
def count_positional_arguments(self):
|
||||||
|
count = 0
|
||||||
|
for star_count, key_start, had_equal in self._list_arguments()[:-1]:
|
||||||
|
if star_count:
|
||||||
|
break
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
def _iter_arguments(nodes, position):
|
def _iter_arguments(nodes, position):
|
||||||
def remove_after_pos(name):
|
def remove_after_pos(name):
|
||||||
@@ -268,8 +296,7 @@ def _iter_arguments(nodes, position):
|
|||||||
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
|
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
|
||||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||||
if nodes_before[-1].type == 'arglist':
|
if nodes_before[-1].type == 'arglist':
|
||||||
for x in _iter_arguments(nodes_before[-1].children, position):
|
yield from _iter_arguments(nodes_before[-1].children, position)
|
||||||
yield x # Python 2 :(
|
|
||||||
return
|
return
|
||||||
|
|
||||||
previous_node_yielded = False
|
previous_node_yielded = False
|
||||||
@@ -294,7 +321,7 @@ def _iter_arguments(nodes, position):
|
|||||||
else:
|
else:
|
||||||
yield 0, None, False
|
yield 0, None, False
|
||||||
stars_seen = 0
|
stars_seen = 0
|
||||||
elif node.type in ('testlist', 'testlist_star_expr'): # testlist is Python 2
|
elif node.type == 'testlist_star_expr':
|
||||||
for n in node.children[::2]:
|
for n in node.children[::2]:
|
||||||
if n.type == 'star_expr':
|
if n.type == 'star_expr':
|
||||||
stars_seen = 1
|
stars_seen = 1
|
||||||
@@ -376,7 +403,7 @@ def get_signature_details(module, position):
|
|||||||
# parents for possible function definitions.
|
# parents for possible function definitions.
|
||||||
node = leaf.parent
|
node = leaf.parent
|
||||||
while node is not None:
|
while node is not None:
|
||||||
if node.type in ('funcdef', 'classdef'):
|
if node.type in ('funcdef', 'classdef', 'decorated', 'async_stmt'):
|
||||||
# Don't show signatures if there's stuff before it that just
|
# Don't show signatures if there's stuff before it that just
|
||||||
# makes it feel strange to have a signature.
|
# makes it feel strange to have a signature.
|
||||||
return None
|
return None
|
||||||
@@ -396,7 +423,8 @@ def get_signature_details(module, position):
|
|||||||
additional_children.insert(0, n)
|
additional_children.insert(0, n)
|
||||||
|
|
||||||
# Find a valid trailer
|
# Find a valid trailer
|
||||||
if node.type == 'trailer' and node.children[0] == '(':
|
if node.type == 'trailer' and node.children[0] == '(' \
|
||||||
|
or node.type == 'decorator' and node.children[2] == '(':
|
||||||
# Additionally we have to check that an ending parenthesis isn't
|
# Additionally we have to check that an ending parenthesis isn't
|
||||||
# interpreted wrong. There are two cases:
|
# interpreted wrong. There are two cases:
|
||||||
# 1. Cursor before paren -> The current signature is good
|
# 1. Cursor before paren -> The current signature is good
|
||||||
@@ -405,7 +433,11 @@ def get_signature_details(module, position):
|
|||||||
leaf = node.get_previous_leaf()
|
leaf = node.get_previous_leaf()
|
||||||
if leaf is None:
|
if leaf is None:
|
||||||
return None
|
return None
|
||||||
return CallDetails(node.children[0], node.children, position)
|
return CallDetails(
|
||||||
|
node.children[0] if node.type == 'trailer' else node.children[2],
|
||||||
|
node.children,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
|
||||||
node = node.parent
|
node = node.parent
|
||||||
|
|
||||||
@@ -444,8 +476,8 @@ def validate_line_column(func):
|
|||||||
line_string = self._code_lines[line - 1]
|
line_string = self._code_lines[line - 1]
|
||||||
line_len = len(line_string)
|
line_len = len(line_string)
|
||||||
if line_string.endswith('\r\n'):
|
if line_string.endswith('\r\n'):
|
||||||
line_len -= 1
|
line_len -= 2
|
||||||
if line_string.endswith('\n'):
|
elif line_string.endswith('\n'):
|
||||||
line_len -= 1
|
line_len -= 1
|
||||||
|
|
||||||
column = line_len if column is None else column
|
column = line_len if column is None else column
|
||||||
@@ -455,3 +487,37 @@ def validate_line_column(func):
|
|||||||
column, line_len, line, line_string))
|
column, line_len, line, line_string))
|
||||||
return func(self, line, column, *args, **kwargs)
|
return func(self, line, column, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def get_module_names(module, all_scopes, definitions=True, references=False):
|
||||||
|
"""
|
||||||
|
Returns a dictionary with name parts as keys and their call paths as
|
||||||
|
values.
|
||||||
|
"""
|
||||||
|
def def_ref_filter(name):
|
||||||
|
is_def = name.is_definition()
|
||||||
|
return definitions and is_def or references and not is_def
|
||||||
|
|
||||||
|
names = list(chain.from_iterable(module.get_used_names().values()))
|
||||||
|
if not all_scopes:
|
||||||
|
# We have to filter all the names that don't have the module as a
|
||||||
|
# parent_scope. There's None as a parent, because nodes in the module
|
||||||
|
# node have the parent module and not suite as all the others.
|
||||||
|
# Therefore it's important to catch that case.
|
||||||
|
|
||||||
|
def is_module_scope_name(name):
|
||||||
|
parent_scope = get_parent_scope(name)
|
||||||
|
# async functions have an extra wrapper. Strip it.
|
||||||
|
if parent_scope and parent_scope.type == 'async_stmt':
|
||||||
|
parent_scope = parent_scope.parent
|
||||||
|
return parent_scope in (module, None)
|
||||||
|
|
||||||
|
names = [n for n in names if is_module_scope_name(n)]
|
||||||
|
return filter(def_ref_filter, names)
|
||||||
|
|
||||||
|
|
||||||
|
def split_search_string(name):
|
||||||
|
type, _, dotted_names = name.rpartition(' ')
|
||||||
|
if type == 'def':
|
||||||
|
type = 'function'
|
||||||
|
return type, dotted_names.split('.')
|
||||||
+44
-11
@@ -3,6 +3,9 @@ TODO Some parts of this module are still not well documented.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from jedi.inference import compiled
|
from jedi.inference import compiled
|
||||||
|
from jedi.inference.base_value import ValueSet
|
||||||
|
from jedi.inference.filters import ParserTreeFilter, MergedFilter
|
||||||
|
from jedi.inference.names import TreeNameDefinition
|
||||||
from jedi.inference.compiled import mixed
|
from jedi.inference.compiled import mixed
|
||||||
from jedi.inference.compiled.access import create_access_path
|
from jedi.inference.compiled.access import create_access_path
|
||||||
from jedi.inference.context import ModuleContext
|
from jedi.inference.context import ModuleContext
|
||||||
@@ -14,15 +17,42 @@ def _create(inference_state, obj):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NamespaceObject(object):
|
class NamespaceObject:
|
||||||
def __init__(self, dct):
|
def __init__(self, dct):
|
||||||
self.__dict__ = dct
|
self.__dict__ = dct
|
||||||
|
|
||||||
|
|
||||||
|
class MixedTreeName(TreeNameDefinition):
|
||||||
|
def infer(self):
|
||||||
|
"""
|
||||||
|
In IPython notebook it is typical that some parts of the code that is
|
||||||
|
provided was already executed. In that case if something is not properly
|
||||||
|
inferred, it should still infer from the variables it already knows.
|
||||||
|
"""
|
||||||
|
inferred = super().infer()
|
||||||
|
if not inferred:
|
||||||
|
for compiled_value in self.parent_context.mixed_values:
|
||||||
|
for f in compiled_value.get_filters():
|
||||||
|
values = ValueSet.from_sets(
|
||||||
|
n.infer() for n in f.get(self.string_name)
|
||||||
|
)
|
||||||
|
if values:
|
||||||
|
return values
|
||||||
|
return inferred
|
||||||
|
|
||||||
|
|
||||||
|
class MixedParserTreeFilter(ParserTreeFilter):
|
||||||
|
name_class = MixedTreeName
|
||||||
|
|
||||||
|
|
||||||
class MixedModuleContext(ModuleContext):
|
class MixedModuleContext(ModuleContext):
|
||||||
def __init__(self, tree_module_value, namespaces):
|
def __init__(self, tree_module_value, namespaces):
|
||||||
super(MixedModuleContext, self).__init__(tree_module_value)
|
super().__init__(tree_module_value)
|
||||||
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
|
self.mixed_values = [
|
||||||
|
self._get_mixed_object(
|
||||||
|
_create(self.inference_state, NamespaceObject(n))
|
||||||
|
) for n in namespaces
|
||||||
|
]
|
||||||
|
|
||||||
def _get_mixed_object(self, compiled_value):
|
def _get_mixed_object(self, compiled_value):
|
||||||
return mixed.MixedObject(
|
return mixed.MixedObject(
|
||||||
@@ -30,12 +60,15 @@ class MixedModuleContext(ModuleContext):
|
|||||||
tree_value=self._value
|
tree_value=self._value
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, until_position=None, origin_scope=None):
|
||||||
for filter in self._value.as_context().get_filters(*args, **kwargs):
|
yield MergedFilter(
|
||||||
yield filter
|
MixedParserTreeFilter(
|
||||||
|
parent_context=self,
|
||||||
|
until_position=until_position,
|
||||||
|
origin_scope=origin_scope
|
||||||
|
),
|
||||||
|
self.get_global_filter(),
|
||||||
|
)
|
||||||
|
|
||||||
for namespace_obj in self._namespace_objects:
|
for mixed_object in self.mixed_values:
|
||||||
compiled_value = _create(self.inference_state, namespace_obj)
|
yield from mixed_object.get_filters(until_position, origin_scope)
|
||||||
mixed_object = self._get_mixed_object(compiled_value)
|
|
||||||
for filter in mixed_object.get_filters(*args, **kwargs):
|
|
||||||
yield filter
|
|
||||||
+10
-15
@@ -1,22 +1,20 @@
|
|||||||
import pydoc
|
import pydoc
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from jedi.inference.utils import ignored
|
|
||||||
from jedi.inference.names import AbstractArbitraryName
|
from jedi.inference.names import AbstractArbitraryName
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pydoc_data import topics as pydoc_topics
|
# https://github.com/python/typeshed/pull/4351 adds pydoc_data
|
||||||
|
from pydoc_data import topics # type: ignore[import]
|
||||||
|
pydoc_topics: Optional[Dict[str, str]] = topics.topics
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Python 2
|
# Python 3.6.8 embeddable does not have pydoc_data.
|
||||||
try:
|
pydoc_topics = None
|
||||||
import pydoc_topics
|
|
||||||
except ImportError:
|
|
||||||
# This is for Python 3 embeddable version, which dont have
|
|
||||||
# pydoc_data module in its file python3x.zip.
|
|
||||||
pydoc_topics = None
|
|
||||||
|
|
||||||
|
|
||||||
class KeywordName(AbstractArbitraryName):
|
class KeywordName(AbstractArbitraryName):
|
||||||
api_type = u'keyword'
|
api_type = 'keyword'
|
||||||
|
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
return imitate_pydoc(self.string_name)
|
return imitate_pydoc(self.string_name)
|
||||||
@@ -30,11 +28,8 @@ def imitate_pydoc(string):
|
|||||||
if pydoc_topics is None:
|
if pydoc_topics is None:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# str needed because of possible unicode stuff in py2k (pydoc doesn't work
|
|
||||||
# with unicode strings)
|
|
||||||
string = str(string)
|
|
||||||
h = pydoc.help
|
h = pydoc.help
|
||||||
with ignored(KeyError):
|
with suppress(KeyError):
|
||||||
# try to access symbols
|
# try to access symbols
|
||||||
string = h.symbols[string]
|
string = h.symbols[string]
|
||||||
string, _, related = string.partition(' ')
|
string, _, related = string.partition(' ')
|
||||||
@@ -52,6 +47,6 @@ def imitate_pydoc(string):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return pydoc_topics.topics[label].strip() if pydoc_topics else ''
|
return pydoc_topics[label].strip() if pydoc_topics else ''
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return ''
|
return ''
|
||||||
+304
-69
@@ -1,21 +1,52 @@
|
|||||||
import os
|
"""
|
||||||
import json
|
Projects are a way to handle Python projects within Jedi. For simpler plugins
|
||||||
|
you might not want to deal with projects, but if you want to give the user more
|
||||||
|
flexibility to define sys paths and Python interpreters for a project,
|
||||||
|
:class:`.Project` is the perfect way to allow for that.
|
||||||
|
|
||||||
from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError
|
Projects can be saved to disk and loaded again, to allow project definitions to
|
||||||
from jedi.api.environment import SameEnvironment, \
|
be used across repositories.
|
||||||
get_cached_default_environment
|
"""
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from jedi import debug
|
||||||
|
from jedi.api.environment import get_cached_default_environment, create_environment
|
||||||
from jedi.api.exceptions import WrongVersion
|
from jedi.api.exceptions import WrongVersion
|
||||||
from jedi._compatibility import force_unicode
|
from jedi.api.completion import search_in_module
|
||||||
|
from jedi.api.helpers import split_search_string, get_module_names
|
||||||
|
from jedi.inference.imports import load_module_from_path, \
|
||||||
|
load_namespace_from_path, iter_module_names
|
||||||
from jedi.inference.sys_path import discover_buildout_paths
|
from jedi.inference.sys_path import discover_buildout_paths
|
||||||
from jedi.inference.cache import inference_state_as_method_param_cache
|
from jedi.inference.cache import inference_state_as_method_param_cache
|
||||||
from jedi.common.utils import traverse_parents
|
from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
|
||||||
|
from jedi.file_io import FolderIO
|
||||||
|
|
||||||
_CONFIG_FOLDER = '.jedi'
|
_CONFIG_FOLDER = '.jedi'
|
||||||
_CONTAINS_POTENTIAL_PROJECT = 'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in'
|
_CONTAINS_POTENTIAL_PROJECT = \
|
||||||
|
'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in', 'pyproject.toml'
|
||||||
|
|
||||||
_SERIALIZER_VERSION = 1
|
_SERIALIZER_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
|
def _try_to_skip_duplicates(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
found_tree_nodes = []
|
||||||
|
found_modules = []
|
||||||
|
for definition in func(*args, **kwargs):
|
||||||
|
tree_node = definition._name.tree_name
|
||||||
|
if tree_node is not None and tree_node in found_tree_nodes:
|
||||||
|
continue
|
||||||
|
if definition.type == 'module' and definition.module_path is not None:
|
||||||
|
if definition.module_path in found_modules:
|
||||||
|
continue
|
||||||
|
found_modules.append(definition.module_path)
|
||||||
|
yield definition
|
||||||
|
found_tree_nodes.append(tree_node)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def _remove_duplicates_from_path(path):
|
def _remove_duplicates_from_path(path):
|
||||||
used = set()
|
used = set()
|
||||||
for p in path:
|
for p in path:
|
||||||
@@ -25,68 +56,129 @@ def _remove_duplicates_from_path(path):
|
|||||||
yield p
|
yield p
|
||||||
|
|
||||||
|
|
||||||
def _force_unicode_list(lst):
|
class Project:
|
||||||
return list(map(force_unicode, lst))
|
"""
|
||||||
|
Projects are a simple way to manage Python folders and define how Jedi does
|
||||||
|
import resolution. It is mostly used as a parameter to :class:`.Script`.
|
||||||
class Project(object):
|
Additionally there are functions to search a whole project.
|
||||||
# TODO serialize environment
|
"""
|
||||||
_serializer_ignore_attributes = ('_environment',)
|
|
||||||
_environment = None
|
_environment = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_config_folder_path(base_path):
|
||||||
|
return base_path.joinpath(_CONFIG_FOLDER)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_json_path(base_path):
|
def _get_json_path(base_path):
|
||||||
return os.path.join(base_path, _CONFIG_FOLDER, 'project.json')
|
return Project._get_config_folder_path(base_path).joinpath('project.json')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, path):
|
def load(cls, path):
|
||||||
"""
|
"""
|
||||||
|
Loads a project from a specific path. You should not provide the path
|
||||||
|
to ``.jedi/project.json``, but rather the path to the project folder.
|
||||||
|
|
||||||
:param path: The path of the directory you want to use as a project.
|
:param path: The path of the directory you want to use as a project.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(path, str):
|
||||||
|
path = Path(path)
|
||||||
with open(cls._get_json_path(path)) as f:
|
with open(cls._get_json_path(path)) as f:
|
||||||
version, data = json.load(f)
|
version, data = json.load(f)
|
||||||
|
|
||||||
if version == 1:
|
if version == 1:
|
||||||
self = cls.__new__()
|
return cls(**data)
|
||||||
self.__dict__.update(data)
|
|
||||||
return self
|
|
||||||
else:
|
else:
|
||||||
raise WrongVersion(
|
raise WrongVersion(
|
||||||
"The Jedi version of this project seems newer than what we can handle."
|
"The Jedi version of this project seems newer than what we can handle."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Saves the project configuration in the project in ``.jedi/project.json``.
|
||||||
|
"""
|
||||||
|
data = dict(self.__dict__)
|
||||||
|
data.pop('_environment', None)
|
||||||
|
data.pop('_django', None) # TODO make django setting public?
|
||||||
|
data = {k.lstrip('_'): v for k, v in data.items()}
|
||||||
|
data['path'] = str(data['path'])
|
||||||
|
|
||||||
|
self._get_config_folder_path(self._path).mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(self._get_json_path(self._path), 'w') as f:
|
||||||
|
return json.dump((_SERIALIZER_VERSION, data), f)
|
||||||
|
|
||||||
def __init__(self, path, **kwargs):
|
def __init__(self, path, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param path: The base path for this project.
|
:param path: The base path for this project.
|
||||||
|
:param environment_path: The Python executable path, typically the path
|
||||||
|
of a virtual environment.
|
||||||
|
:param load_unsafe_extensions: Default False, Loads extensions that are not in the
|
||||||
|
sys path and in the local directories. With this option enabled,
|
||||||
|
this is potentially unsafe if you clone a git repository and
|
||||||
|
analyze it's code, because those compiled extensions will be
|
||||||
|
important and therefore have execution privileges.
|
||||||
:param sys_path: list of str. You can override the sys path if you
|
:param sys_path: list of str. You can override the sys path if you
|
||||||
want. By default the ``sys.path.`` is generated from the
|
want. By default the ``sys.path.`` is generated by the
|
||||||
environment (virtualenvs, etc).
|
environment (virtualenvs, etc).
|
||||||
|
:param added_sys_path: list of str. Adds these paths at the end of the
|
||||||
|
sys path.
|
||||||
:param smart_sys_path: If this is enabled (default), adds paths from
|
:param smart_sys_path: If this is enabled (default), adds paths from
|
||||||
local directories. Otherwise you will have to rely on your packages
|
local directories. Otherwise you will have to rely on your packages
|
||||||
being properly configured on the ``sys.path``.
|
being properly configured on the ``sys.path``.
|
||||||
"""
|
"""
|
||||||
def py2_comp(path, environment=None, sys_path=None,
|
def py2_comp(path, environment_path=None, load_unsafe_extensions=False,
|
||||||
smart_sys_path=True, _django=False):
|
sys_path=None, added_sys_path=(), smart_sys_path=True):
|
||||||
self._path = os.path.abspath(path)
|
if isinstance(path, str):
|
||||||
if isinstance(environment, SameEnvironment):
|
path = Path(path).absolute()
|
||||||
self._environment = environment
|
self._path = path
|
||||||
|
|
||||||
|
self._environment_path = environment_path
|
||||||
|
if sys_path is not None:
|
||||||
|
# Remap potential pathlib.Path entries
|
||||||
|
sys_path = list(map(str, sys_path))
|
||||||
self._sys_path = sys_path
|
self._sys_path = sys_path
|
||||||
self._smart_sys_path = smart_sys_path
|
self._smart_sys_path = smart_sys_path
|
||||||
self._django = _django
|
self._load_unsafe_extensions = load_unsafe_extensions
|
||||||
|
self._django = False
|
||||||
|
# Remap potential pathlib.Path entries
|
||||||
|
self.added_sys_path = list(map(str, added_sys_path))
|
||||||
|
"""The sys path that is going to be added at the end of the """
|
||||||
|
|
||||||
py2_comp(path, **kwargs)
|
py2_comp(path, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
"""
|
||||||
|
The base path for this project.
|
||||||
|
"""
|
||||||
|
return self._path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sys_path(self):
|
||||||
|
"""
|
||||||
|
The sys path provided to this project. This can be None and in that
|
||||||
|
case will be auto generated.
|
||||||
|
"""
|
||||||
|
return self._sys_path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def smart_sys_path(self):
|
||||||
|
"""
|
||||||
|
If the sys path is going to be calculated in a smart way, where
|
||||||
|
additional paths are added.
|
||||||
|
"""
|
||||||
|
return self._smart_sys_path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def load_unsafe_extensions(self):
|
||||||
|
"""
|
||||||
|
Wheter the project loads unsafe extensions.
|
||||||
|
"""
|
||||||
|
return self._load_unsafe_extensions
|
||||||
|
|
||||||
@inference_state_as_method_param_cache()
|
@inference_state_as_method_param_cache()
|
||||||
def _get_base_sys_path(self, inference_state, environment=None):
|
def _get_base_sys_path(self, inference_state):
|
||||||
if self._sys_path is not None:
|
|
||||||
return self._sys_path
|
|
||||||
|
|
||||||
# The sys path has not been set explicitly.
|
# The sys path has not been set explicitly.
|
||||||
if environment is None:
|
sys_path = list(inference_state.environment.get_sys_path())
|
||||||
environment = self.get_environment()
|
|
||||||
|
|
||||||
sys_path = list(environment.get_sys_path())
|
|
||||||
try:
|
try:
|
||||||
sys_path.remove('')
|
sys_path.remove('')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -94,34 +186,41 @@ class Project(object):
|
|||||||
return sys_path
|
return sys_path
|
||||||
|
|
||||||
@inference_state_as_method_param_cache()
|
@inference_state_as_method_param_cache()
|
||||||
def _get_sys_path(self, inference_state, environment=None,
|
def _get_sys_path(self, inference_state, add_parent_paths=True, add_init_paths=False):
|
||||||
add_parent_paths=True, add_init_paths=False):
|
|
||||||
"""
|
"""
|
||||||
Keep this method private for all users of jedi. However internally this
|
Keep this method private for all users of jedi. However internally this
|
||||||
one is used like a public method.
|
one is used like a public method.
|
||||||
"""
|
"""
|
||||||
suffixed = []
|
suffixed = list(self.added_sys_path)
|
||||||
prefixed = []
|
prefixed = []
|
||||||
|
|
||||||
sys_path = list(self._get_base_sys_path(inference_state, environment))
|
if self._sys_path is None:
|
||||||
|
sys_path = list(self._get_base_sys_path(inference_state))
|
||||||
|
else:
|
||||||
|
sys_path = list(self._sys_path)
|
||||||
|
|
||||||
if self._smart_sys_path:
|
if self._smart_sys_path:
|
||||||
prefixed.append(self._path)
|
prefixed.append(str(self._path))
|
||||||
|
|
||||||
if inference_state.script_path is not None:
|
if inference_state.script_path is not None:
|
||||||
suffixed += discover_buildout_paths(inference_state, inference_state.script_path)
|
suffixed += map(str, discover_buildout_paths(
|
||||||
|
inference_state,
|
||||||
|
inference_state.script_path
|
||||||
|
))
|
||||||
|
|
||||||
if add_parent_paths:
|
if add_parent_paths:
|
||||||
# Collect directories in upward search by:
|
# Collect directories in upward search by:
|
||||||
# 1. Skipping directories with __init__.py
|
# 1. Skipping directories with __init__.py
|
||||||
# 2. Stopping immediately when above self._path
|
# 2. Stopping immediately when above self._path
|
||||||
traversed = []
|
traversed = []
|
||||||
for parent_path in traverse_parents(inference_state.script_path):
|
for parent_path in inference_state.script_path.parents:
|
||||||
if not parent_path.startswith(self._path):
|
if parent_path == self._path \
|
||||||
|
or self._path not in parent_path.parents:
|
||||||
break
|
break
|
||||||
if not add_init_paths \
|
if not add_init_paths \
|
||||||
and os.path.isfile(os.path.join(parent_path, "__init__.py")):
|
and parent_path.joinpath("__init__.py").is_file():
|
||||||
continue
|
continue
|
||||||
traversed.append(parent_path)
|
traversed.append(str(parent_path))
|
||||||
|
|
||||||
# AFAIK some libraries have imports like `foo.foo.bar`, which
|
# AFAIK some libraries have imports like `foo.foo.bar`, which
|
||||||
# leads to the conclusion to by default prefer longer paths
|
# leads to the conclusion to by default prefer longer paths
|
||||||
@@ -129,70 +228,199 @@ class Project(object):
|
|||||||
suffixed += reversed(traversed)
|
suffixed += reversed(traversed)
|
||||||
|
|
||||||
if self._django:
|
if self._django:
|
||||||
prefixed.append(self._path)
|
prefixed.append(str(self._path))
|
||||||
|
|
||||||
path = prefixed + sys_path + suffixed
|
path = prefixed + sys_path + suffixed
|
||||||
return list(_force_unicode_list(_remove_duplicates_from_path(path)))
|
return list(_remove_duplicates_from_path(path))
|
||||||
|
|
||||||
def save(self):
|
|
||||||
data = dict(self.__dict__)
|
|
||||||
for attribute in self._serializer_ignore_attributes:
|
|
||||||
data.pop(attribute, None)
|
|
||||||
|
|
||||||
with open(self._get_json_path(self._path), 'wb') as f:
|
|
||||||
return json.dump((_SERIALIZER_VERSION, data), f)
|
|
||||||
|
|
||||||
def get_environment(self):
|
def get_environment(self):
|
||||||
if self._environment is None:
|
if self._environment is None:
|
||||||
return get_cached_default_environment()
|
if self._environment_path is not None:
|
||||||
|
self._environment = create_environment(self._environment_path, safe=False)
|
||||||
|
else:
|
||||||
|
self._environment = get_cached_default_environment()
|
||||||
return self._environment
|
return self._environment
|
||||||
|
|
||||||
|
def search(self, string, *, all_scopes=False):
|
||||||
|
"""
|
||||||
|
Searches a name in the whole project. If the project is very big,
|
||||||
|
at some point Jedi will stop searching. However it's also very much
|
||||||
|
recommended to not exhaust the generator. Just display the first ten
|
||||||
|
results to the user.
|
||||||
|
|
||||||
|
There are currently three different search patterns:
|
||||||
|
|
||||||
|
- ``foo`` to search for a definition foo in any file or a file called
|
||||||
|
``foo.py`` or ``foo.pyi``.
|
||||||
|
- ``foo.bar`` to search for the ``foo`` and then an attribute ``bar``
|
||||||
|
in it.
|
||||||
|
- ``class foo.bar.Bar`` or ``def foo.bar.baz`` to search for a specific
|
||||||
|
API type.
|
||||||
|
|
||||||
|
:param bool all_scopes: Default False; searches not only for
|
||||||
|
definitions on the top level of a module level, but also in
|
||||||
|
functions and classes.
|
||||||
|
:yields: :class:`.Name`
|
||||||
|
"""
|
||||||
|
return self._search_func(string, all_scopes=all_scopes)
|
||||||
|
|
||||||
|
def complete_search(self, string, **kwargs):
|
||||||
|
"""
|
||||||
|
Like :meth:`.Script.search`, but completes that string. An empty string
|
||||||
|
lists all definitions in a project, so be careful with that.
|
||||||
|
|
||||||
|
:param bool all_scopes: Default False; searches not only for
|
||||||
|
definitions on the top level of a module level, but also in
|
||||||
|
functions and classes.
|
||||||
|
:yields: :class:`.Completion`
|
||||||
|
"""
|
||||||
|
return self._search_func(string, complete=True, **kwargs)
|
||||||
|
|
||||||
|
@_try_to_skip_duplicates
|
||||||
|
def _search_func(self, string, complete=False, all_scopes=False):
|
||||||
|
# Using a Script is they easiest way to get an empty module context.
|
||||||
|
from jedi import Script
|
||||||
|
s = Script('', project=self)
|
||||||
|
inference_state = s._inference_state
|
||||||
|
empty_module_context = s._get_module_context()
|
||||||
|
|
||||||
|
debug.dbg('Search for string %s, complete=%s', string, complete)
|
||||||
|
wanted_type, wanted_names = split_search_string(string)
|
||||||
|
name = wanted_names[0]
|
||||||
|
stub_folder_name = name + '-stubs'
|
||||||
|
|
||||||
|
ios = recurse_find_python_folders_and_files(FolderIO(str(self._path)))
|
||||||
|
file_ios = []
|
||||||
|
|
||||||
|
# 1. Search for modules in the current project
|
||||||
|
for folder_io, file_io in ios:
|
||||||
|
if file_io is None:
|
||||||
|
file_name = folder_io.get_base_name()
|
||||||
|
if file_name == name or file_name == stub_folder_name:
|
||||||
|
f = folder_io.get_file_io('__init__.py')
|
||||||
|
try:
|
||||||
|
m = load_module_from_path(inference_state, f).as_context()
|
||||||
|
except FileNotFoundError:
|
||||||
|
f = folder_io.get_file_io('__init__.pyi')
|
||||||
|
try:
|
||||||
|
m = load_module_from_path(inference_state, f).as_context()
|
||||||
|
except FileNotFoundError:
|
||||||
|
m = load_namespace_from_path(inference_state, folder_io).as_context()
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
file_ios.append(file_io)
|
||||||
|
if Path(file_io.path).name in (name + '.py', name + '.pyi'):
|
||||||
|
m = load_module_from_path(inference_state, file_io).as_context()
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
debug.dbg('Search of a specific module %s', m)
|
||||||
|
yield from search_in_module(
|
||||||
|
inference_state,
|
||||||
|
m,
|
||||||
|
names=[m.name],
|
||||||
|
wanted_type=wanted_type,
|
||||||
|
wanted_names=wanted_names,
|
||||||
|
complete=complete,
|
||||||
|
convert=True,
|
||||||
|
ignore_imports=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Search for identifiers in the project.
|
||||||
|
for module_context in search_in_file_ios(inference_state, file_ios, name):
|
||||||
|
names = get_module_names(module_context.tree_node, all_scopes=all_scopes)
|
||||||
|
names = [module_context.create_name(n) for n in names]
|
||||||
|
names = _remove_imports(names)
|
||||||
|
yield from search_in_module(
|
||||||
|
inference_state,
|
||||||
|
module_context,
|
||||||
|
names=names,
|
||||||
|
wanted_type=wanted_type,
|
||||||
|
wanted_names=wanted_names,
|
||||||
|
complete=complete,
|
||||||
|
ignore_imports=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Search for modules on sys.path
|
||||||
|
sys_path = [
|
||||||
|
p for p in self._get_sys_path(inference_state)
|
||||||
|
# Exclude folders that are handled by recursing of the Python
|
||||||
|
# folders.
|
||||||
|
if not p.startswith(str(self._path))
|
||||||
|
]
|
||||||
|
names = list(iter_module_names(inference_state, empty_module_context, sys_path))
|
||||||
|
yield from search_in_module(
|
||||||
|
inference_state,
|
||||||
|
empty_module_context,
|
||||||
|
names=names,
|
||||||
|
wanted_type=wanted_type,
|
||||||
|
wanted_names=wanted_names,
|
||||||
|
complete=complete,
|
||||||
|
convert=True,
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self._path)
|
return '<%s: %s>' % (self.__class__.__name__, self._path)
|
||||||
|
|
||||||
|
|
||||||
def _is_potential_project(path):
|
def _is_potential_project(path):
|
||||||
for name in _CONTAINS_POTENTIAL_PROJECT:
|
for name in _CONTAINS_POTENTIAL_PROJECT:
|
||||||
if os.path.exists(os.path.join(path, name)):
|
try:
|
||||||
return True
|
if path.joinpath(name).exists():
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _is_django_path(directory):
|
def _is_django_path(directory):
|
||||||
""" Detects the path of the very well known Django library (if used) """
|
""" Detects the path of the very well known Django library (if used) """
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(directory, 'manage.py'), 'rb') as f:
|
with open(directory.joinpath('manage.py'), 'rb') as f:
|
||||||
return b"DJANGO_SETTINGS_MODULE" in f.read()
|
return b"DJANGO_SETTINGS_MODULE" in f.read()
|
||||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_project(path=None):
|
def get_default_project(path=None):
|
||||||
if path is None:
|
"""
|
||||||
path = os.getcwd()
|
If a project is not defined by the user, Jedi tries to define a project by
|
||||||
|
itself as well as possible. Jedi traverses folders until it finds one of
|
||||||
|
the following:
|
||||||
|
|
||||||
check = os.path.realpath(path)
|
1. A ``.jedi/config.json``
|
||||||
|
2. One of the following files: ``setup.py``, ``.git``, ``.hg``,
|
||||||
|
``requirements.txt`` and ``MANIFEST.in``.
|
||||||
|
"""
|
||||||
|
if path is None:
|
||||||
|
path = Path.cwd()
|
||||||
|
elif isinstance(path, str):
|
||||||
|
path = Path(path)
|
||||||
|
|
||||||
|
check = path.absolute()
|
||||||
probable_path = None
|
probable_path = None
|
||||||
first_no_init_file = None
|
first_no_init_file = None
|
||||||
for dir in traverse_parents(check, include_current=True):
|
for dir in chain([check], check.parents):
|
||||||
try:
|
try:
|
||||||
return Project.load(dir)
|
return Project.load(dir)
|
||||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||||
pass
|
pass
|
||||||
|
except NotADirectoryError:
|
||||||
|
continue
|
||||||
|
|
||||||
if first_no_init_file is None:
|
if first_no_init_file is None:
|
||||||
if os.path.exists(os.path.join(dir, '__init__.py')):
|
if dir.joinpath('__init__.py').exists():
|
||||||
# In the case that a __init__.py exists, it's in 99% just a
|
# In the case that a __init__.py exists, it's in 99% just a
|
||||||
# Python package and the project sits at least one level above.
|
# Python package and the project sits at least one level above.
|
||||||
continue
|
continue
|
||||||
else:
|
elif not dir.is_file():
|
||||||
first_no_init_file = dir
|
first_no_init_file = dir
|
||||||
|
|
||||||
if _is_django_path(dir):
|
if _is_django_path(dir):
|
||||||
return Project(dir, _django=True)
|
project = Project(dir)
|
||||||
|
project._django = True
|
||||||
|
return project
|
||||||
|
|
||||||
if probable_path is None and _is_potential_project(dir):
|
if probable_path is None and _is_potential_project(dir):
|
||||||
probable_path = dir
|
probable_path = dir
|
||||||
@@ -204,5 +432,12 @@ def get_default_project(path=None):
|
|||||||
if first_no_init_file is not None:
|
if first_no_init_file is not None:
|
||||||
return Project(first_no_init_file)
|
return Project(first_no_init_file)
|
||||||
|
|
||||||
curdir = path if os.path.isdir(path) else os.path.dirname(path)
|
curdir = path if path.is_dir() else path.parent
|
||||||
return Project(curdir)
|
return Project(curdir)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_imports(names):
|
||||||
|
return [
|
||||||
|
n for n in names
|
||||||
|
if n.tree_name is None or n.api_type not in ('module', 'namespace')
|
||||||
|
]
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
import difflib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Iterable, Tuple
|
||||||
|
|
||||||
|
from parso import split_lines
|
||||||
|
|
||||||
|
from jedi.api.exceptions import RefactoringError
|
||||||
|
|
||||||
|
EXPRESSION_PARTS = (
|
||||||
|
'or_test and_test not_test comparison '
|
||||||
|
'expr xor_expr and_expr shift_expr arith_expr term factor power atom_expr'
|
||||||
|
).split()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangedFile:
|
||||||
|
def __init__(self, inference_state, from_path, to_path,
|
||||||
|
module_node, node_to_str_map):
|
||||||
|
self._inference_state = inference_state
|
||||||
|
self._from_path = from_path
|
||||||
|
self._to_path = to_path
|
||||||
|
self._module_node = module_node
|
||||||
|
self._node_to_str_map = node_to_str_map
|
||||||
|
|
||||||
|
def get_diff(self):
|
||||||
|
old_lines = split_lines(self._module_node.get_code(), keepends=True)
|
||||||
|
new_lines = split_lines(self.get_new_code(), keepends=True)
|
||||||
|
|
||||||
|
# Add a newline at the end if it's missing. Otherwise the diff will be
|
||||||
|
# very weird. A `diff -u file1 file2` would show the string:
|
||||||
|
#
|
||||||
|
# \ No newline at end of file
|
||||||
|
#
|
||||||
|
# This is not necessary IMO, because Jedi does not really play with
|
||||||
|
# newlines and the ending newline does not really matter in Python
|
||||||
|
# files. ~dave
|
||||||
|
if old_lines[-1] != '':
|
||||||
|
old_lines[-1] += '\n'
|
||||||
|
if new_lines[-1] != '':
|
||||||
|
new_lines[-1] += '\n'
|
||||||
|
|
||||||
|
project_path = self._inference_state.project.path
|
||||||
|
if self._from_path is None:
|
||||||
|
from_p = ''
|
||||||
|
else:
|
||||||
|
from_p = self._from_path.relative_to(project_path)
|
||||||
|
if self._to_path is None:
|
||||||
|
to_p = ''
|
||||||
|
else:
|
||||||
|
to_p = self._to_path.relative_to(project_path)
|
||||||
|
diff = difflib.unified_diff(
|
||||||
|
old_lines, new_lines,
|
||||||
|
fromfile=str(from_p),
|
||||||
|
tofile=str(to_p),
|
||||||
|
)
|
||||||
|
# Apparently there's a space at the end of the diff - for whatever
|
||||||
|
# reason.
|
||||||
|
return ''.join(diff).rstrip(' ')
|
||||||
|
|
||||||
|
def get_new_code(self):
|
||||||
|
return self._inference_state.grammar.refactor(self._module_node, self._node_to_str_map)
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
if self._from_path is None:
|
||||||
|
raise RefactoringError(
|
||||||
|
'Cannot apply a refactoring on a Script with path=None'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(self._from_path, 'w', newline='') as f:
|
||||||
|
f.write(self.get_new_code())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self._from_path)
|
||||||
|
|
||||||
|
|
||||||
|
class Refactoring:
|
||||||
|
def __init__(self, inference_state, file_to_node_changes, renames=()):
|
||||||
|
self._inference_state = inference_state
|
||||||
|
self._renames = renames
|
||||||
|
self._file_to_node_changes = file_to_node_changes
|
||||||
|
|
||||||
|
def get_changed_files(self) -> Dict[Path, ChangedFile]:
|
||||||
|
def calculate_to_path(p):
|
||||||
|
if p is None:
|
||||||
|
return p
|
||||||
|
p = str(p)
|
||||||
|
for from_, to in renames:
|
||||||
|
if p.startswith(str(from_)):
|
||||||
|
p = str(to) + p[len(str(from_)):]
|
||||||
|
return Path(p)
|
||||||
|
|
||||||
|
renames = self.get_renames()
|
||||||
|
return {
|
||||||
|
path: ChangedFile(
|
||||||
|
self._inference_state,
|
||||||
|
from_path=path,
|
||||||
|
to_path=calculate_to_path(path),
|
||||||
|
module_node=next(iter(map_)).get_root_node(),
|
||||||
|
node_to_str_map=map_
|
||||||
|
) for path, map_ in sorted(self._file_to_node_changes.items())
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_renames(self) -> Iterable[Tuple[Path, Path]]:
|
||||||
|
"""
|
||||||
|
Files can be renamed in a refactoring.
|
||||||
|
"""
|
||||||
|
return sorted(self._renames)
|
||||||
|
|
||||||
|
def get_diff(self):
|
||||||
|
text = ''
|
||||||
|
project_path = self._inference_state.project.path
|
||||||
|
for from_, to in self.get_renames():
|
||||||
|
text += 'rename from %s\nrename to %s\n' \
|
||||||
|
% (from_.relative_to(project_path), to.relative_to(project_path))
|
||||||
|
|
||||||
|
return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
"""
|
||||||
|
Applies the whole refactoring to the files, which includes renames.
|
||||||
|
"""
|
||||||
|
for f in self.get_changed_files().values():
|
||||||
|
f.apply()
|
||||||
|
|
||||||
|
for old, new in self.get_renames():
|
||||||
|
old.rename(new)
|
||||||
|
|
||||||
|
|
||||||
|
def _calculate_rename(path, new_name):
|
||||||
|
dir_ = path.parent
|
||||||
|
if path.name in ('__init__.py', '__init__.pyi'):
|
||||||
|
return dir_, dir_.parent.joinpath(new_name)
|
||||||
|
return path, dir_.joinpath(new_name + path.suffix)
|
||||||
|
|
||||||
|
|
||||||
|
def rename(inference_state, definitions, new_name):
|
||||||
|
file_renames = set()
|
||||||
|
file_tree_name_map = {}
|
||||||
|
|
||||||
|
if not definitions:
|
||||||
|
raise RefactoringError("There is no name under the cursor")
|
||||||
|
|
||||||
|
for d in definitions:
|
||||||
|
tree_name = d._name.tree_name
|
||||||
|
if d.type == 'module' and tree_name is None:
|
||||||
|
p = None if d.module_path is None else Path(d.module_path)
|
||||||
|
file_renames.add(_calculate_rename(p, new_name))
|
||||||
|
else:
|
||||||
|
# This private access is ok in a way. It's not public to
|
||||||
|
# protect Jedi users from seeing it.
|
||||||
|
if tree_name is not None:
|
||||||
|
fmap = file_tree_name_map.setdefault(d.module_path, {})
|
||||||
|
fmap[tree_name] = tree_name.prefix + new_name
|
||||||
|
return Refactoring(inference_state, file_tree_name_map, file_renames)
|
||||||
|
|
||||||
|
|
||||||
|
def inline(inference_state, names):
|
||||||
|
if not names:
|
||||||
|
raise RefactoringError("There is no name under the cursor")
|
||||||
|
if any(n.api_type in ('module', 'namespace') for n in names):
|
||||||
|
raise RefactoringError("Cannot inline imports, modules or namespaces")
|
||||||
|
if any(n.tree_name is None for n in names):
|
||||||
|
raise RefactoringError("Cannot inline builtins/extensions")
|
||||||
|
|
||||||
|
definitions = [n for n in names if n.tree_name.is_definition()]
|
||||||
|
if len(definitions) == 0:
|
||||||
|
raise RefactoringError("No definition found to inline")
|
||||||
|
if len(definitions) > 1:
|
||||||
|
raise RefactoringError("Cannot inline a name with multiple definitions")
|
||||||
|
if len(names) == 1:
|
||||||
|
raise RefactoringError("There are no references to this name")
|
||||||
|
|
||||||
|
tree_name = definitions[0].tree_name
|
||||||
|
|
||||||
|
expr_stmt = tree_name.get_definition()
|
||||||
|
if expr_stmt.type != 'expr_stmt':
|
||||||
|
type_ = dict(
|
||||||
|
funcdef='function',
|
||||||
|
classdef='class',
|
||||||
|
).get(expr_stmt.type, expr_stmt.type)
|
||||||
|
raise RefactoringError("Cannot inline a %s" % type_)
|
||||||
|
|
||||||
|
if len(expr_stmt.get_defined_names(include_setitem=True)) > 1:
|
||||||
|
raise RefactoringError("Cannot inline a statement with multiple definitions")
|
||||||
|
first_child = expr_stmt.children[1]
|
||||||
|
if first_child.type == 'annassign' and len(first_child.children) == 4:
|
||||||
|
first_child = first_child.children[2]
|
||||||
|
if first_child != '=':
|
||||||
|
if first_child.type == 'annassign':
|
||||||
|
raise RefactoringError(
|
||||||
|
'Cannot inline a statement that is defined by an annotation'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise RefactoringError(
|
||||||
|
'Cannot inline a statement with "%s"'
|
||||||
|
% first_child.get_code(include_prefix=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
rhs = expr_stmt.get_rhs()
|
||||||
|
replace_code = rhs.get_code(include_prefix=False)
|
||||||
|
|
||||||
|
references = [n for n in names if not n.tree_name.is_definition()]
|
||||||
|
file_to_node_changes = {}
|
||||||
|
for name in references:
|
||||||
|
tree_name = name.tree_name
|
||||||
|
path = name.get_root_context().py__file__()
|
||||||
|
s = replace_code
|
||||||
|
if rhs.type == 'testlist_star_expr' \
|
||||||
|
or tree_name.parent.type in EXPRESSION_PARTS \
|
||||||
|
or tree_name.parent.type == 'trailer' \
|
||||||
|
and tree_name.parent.get_next_sibling() is not None:
|
||||||
|
s = '(' + replace_code + ')'
|
||||||
|
|
||||||
|
of_path = file_to_node_changes.setdefault(path, {})
|
||||||
|
|
||||||
|
n = tree_name
|
||||||
|
prefix = n.prefix
|
||||||
|
par = n.parent
|
||||||
|
if par.type == 'trailer' and par.children[0] == '.':
|
||||||
|
prefix = par.parent.children[0].prefix
|
||||||
|
n = par
|
||||||
|
for some_node in par.parent.children[:par.parent.children.index(par)]:
|
||||||
|
of_path[some_node] = ''
|
||||||
|
of_path[n] = prefix + s
|
||||||
|
|
||||||
|
path = definitions[0].get_root_context().py__file__()
|
||||||
|
changes = file_to_node_changes.setdefault(path, {})
|
||||||
|
changes[expr_stmt] = _remove_indent_of_prefix(expr_stmt.get_first_leaf().prefix)
|
||||||
|
next_leaf = expr_stmt.get_next_leaf()
|
||||||
|
|
||||||
|
# Most of the time we have to remove the newline at the end of the
|
||||||
|
# statement, but if there's a comment we might not need to.
|
||||||
|
if next_leaf.prefix.strip(' \t') == '' \
|
||||||
|
and (next_leaf.type == 'newline' or next_leaf == ';'):
|
||||||
|
changes[next_leaf] = ''
|
||||||
|
return Refactoring(inference_state, file_to_node_changes)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_indent_of_prefix(prefix):
|
||||||
|
r"""
|
||||||
|
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
|
||||||
|
"""
|
||||||
|
return ''.join(split_lines(prefix, keepends=True)[:-1])
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from parso import split_lines
|
||||||
|
|
||||||
|
from jedi import debug
|
||||||
|
from jedi.api.exceptions import RefactoringError
|
||||||
|
from jedi.api.refactoring import Refactoring, EXPRESSION_PARTS
|
||||||
|
from jedi.common import indent_block
|
||||||
|
from jedi.parser_utils import function_is_classmethod, function_is_staticmethod
|
||||||
|
|
||||||
|
|
||||||
|
_DEFINITION_SCOPES = ('suite', 'file_input')
|
||||||
|
_VARIABLE_EXCTRACTABLE = EXPRESSION_PARTS + \
|
||||||
|
('atom testlist_star_expr testlist test lambdef lambdef_nocond '
|
||||||
|
'keyword name number string fstring').split()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_variable(inference_state, path, module_node, name, pos, until_pos):
|
||||||
|
nodes = _find_nodes(module_node, pos, until_pos)
|
||||||
|
debug.dbg('Extracting nodes: %s', nodes)
|
||||||
|
|
||||||
|
is_expression, message = _is_expression_with_error(nodes)
|
||||||
|
if not is_expression:
|
||||||
|
raise RefactoringError(message)
|
||||||
|
|
||||||
|
generated_code = name + ' = ' + _expression_nodes_to_string(nodes)
|
||||||
|
file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)}
|
||||||
|
return Refactoring(inference_state, file_to_node_changes)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_expression_with_error(nodes):
|
||||||
|
"""
|
||||||
|
Returns a tuple (is_expression, error_string).
|
||||||
|
"""
|
||||||
|
if any(node.type == 'name' and node.is_definition() for node in nodes):
|
||||||
|
return False, 'Cannot extract a name that defines something'
|
||||||
|
|
||||||
|
if nodes[0].type not in _VARIABLE_EXCTRACTABLE:
|
||||||
|
return False, 'Cannot extract a "%s"' % nodes[0].type
|
||||||
|
return True, ''
|
||||||
|
|
||||||
|
|
||||||
|
def _find_nodes(module_node, pos, until_pos):
|
||||||
|
"""
|
||||||
|
Looks up a module and tries to find the appropriate amount of nodes that
|
||||||
|
are in there.
|
||||||
|
"""
|
||||||
|
start_node = module_node.get_leaf_for_position(pos, include_prefixes=True)
|
||||||
|
|
||||||
|
if until_pos is None:
|
||||||
|
if start_node.type == 'operator':
|
||||||
|
next_leaf = start_node.get_next_leaf()
|
||||||
|
if next_leaf is not None and next_leaf.start_pos == pos:
|
||||||
|
start_node = next_leaf
|
||||||
|
|
||||||
|
if _is_not_extractable_syntax(start_node):
|
||||||
|
start_node = start_node.parent
|
||||||
|
|
||||||
|
if start_node.parent.type == 'trailer':
|
||||||
|
start_node = start_node.parent.parent
|
||||||
|
while start_node.parent.type in EXPRESSION_PARTS:
|
||||||
|
start_node = start_node.parent
|
||||||
|
|
||||||
|
nodes = [start_node]
|
||||||
|
else:
|
||||||
|
# Get the next leaf if we are at the end of a leaf
|
||||||
|
if start_node.end_pos == pos:
|
||||||
|
next_leaf = start_node.get_next_leaf()
|
||||||
|
if next_leaf is not None:
|
||||||
|
start_node = next_leaf
|
||||||
|
|
||||||
|
# Some syntax is not exactable, just use its parent
|
||||||
|
if _is_not_extractable_syntax(start_node):
|
||||||
|
start_node = start_node.parent
|
||||||
|
|
||||||
|
# Find the end
|
||||||
|
end_leaf = module_node.get_leaf_for_position(until_pos, include_prefixes=True)
|
||||||
|
if end_leaf.start_pos > until_pos:
|
||||||
|
end_leaf = end_leaf.get_previous_leaf()
|
||||||
|
if end_leaf is None:
|
||||||
|
raise RefactoringError('Cannot extract anything from that')
|
||||||
|
|
||||||
|
parent_node = start_node
|
||||||
|
while parent_node.end_pos < end_leaf.end_pos:
|
||||||
|
parent_node = parent_node.parent
|
||||||
|
|
||||||
|
nodes = _remove_unwanted_expression_nodes(parent_node, pos, until_pos)
|
||||||
|
|
||||||
|
# If the user marks just a return statement, we return the expression
|
||||||
|
# instead of the whole statement, because the user obviously wants to
|
||||||
|
# extract that part.
|
||||||
|
if len(nodes) == 1 and start_node.type in ('return_stmt', 'yield_expr'):
|
||||||
|
return [nodes[0].children[1]]
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
def _replace(nodes, expression_replacement, extracted, pos,
|
||||||
|
insert_before_leaf=None, remaining_prefix=None):
|
||||||
|
# Now try to replace the nodes found with a variable and move the code
|
||||||
|
# before the current statement.
|
||||||
|
definition = _get_parent_definition(nodes[0])
|
||||||
|
if insert_before_leaf is None:
|
||||||
|
insert_before_leaf = definition.get_first_leaf()
|
||||||
|
first_node_leaf = nodes[0].get_first_leaf()
|
||||||
|
|
||||||
|
lines = split_lines(insert_before_leaf.prefix, keepends=True)
|
||||||
|
if first_node_leaf is insert_before_leaf:
|
||||||
|
if remaining_prefix is not None:
|
||||||
|
# The remaining prefix has already been calculated.
|
||||||
|
lines[:-1] = remaining_prefix
|
||||||
|
lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n']
|
||||||
|
extracted_prefix = ''.join(lines)
|
||||||
|
|
||||||
|
replacement_dct = {}
|
||||||
|
if first_node_leaf is insert_before_leaf:
|
||||||
|
replacement_dct[nodes[0]] = extracted_prefix + expression_replacement
|
||||||
|
else:
|
||||||
|
if remaining_prefix is None:
|
||||||
|
p = first_node_leaf.prefix
|
||||||
|
else:
|
||||||
|
p = remaining_prefix + _get_indentation(nodes[0])
|
||||||
|
replacement_dct[nodes[0]] = p + expression_replacement
|
||||||
|
replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value
|
||||||
|
|
||||||
|
for node in nodes[1:]:
|
||||||
|
replacement_dct[node] = ''
|
||||||
|
return replacement_dct
|
||||||
|
|
||||||
|
|
||||||
|
def _expression_nodes_to_string(nodes):
|
||||||
|
return ''.join(n.get_code(include_prefix=i != 0) for i, n in enumerate(nodes))
|
||||||
|
|
||||||
|
|
||||||
|
def _suite_nodes_to_string(nodes, pos):
|
||||||
|
n = nodes[0]
|
||||||
|
prefix, part_of_code = _split_prefix_at(n.get_first_leaf(), pos[0] - 1)
|
||||||
|
code = part_of_code + n.get_code(include_prefix=False) \
|
||||||
|
+ ''.join(n.get_code() for n in nodes[1:])
|
||||||
|
return prefix, code
|
||||||
|
|
||||||
|
|
||||||
|
def _split_prefix_at(leaf, until_line):
|
||||||
|
"""
|
||||||
|
Returns a tuple of the leaf's prefix, split at the until_line
|
||||||
|
position.
|
||||||
|
"""
|
||||||
|
# second means the second returned part
|
||||||
|
second_line_count = leaf.start_pos[0] - until_line
|
||||||
|
lines = split_lines(leaf.prefix, keepends=True)
|
||||||
|
return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])
|
||||||
|
|
||||||
|
|
||||||
|
def _get_indentation(node):
|
||||||
|
return split_lines(node.get_first_leaf().prefix)[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_parent_definition(node):
|
||||||
|
"""
|
||||||
|
Returns the statement where a node is defined.
|
||||||
|
"""
|
||||||
|
while node is not None:
|
||||||
|
if node.parent.type in _DEFINITION_SCOPES:
|
||||||
|
return node
|
||||||
|
node = node.parent
|
||||||
|
raise NotImplementedError('We should never even get here')
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_unwanted_expression_nodes(parent_node, pos, until_pos):
|
||||||
|
"""
|
||||||
|
This function makes it so for `1 * 2 + 3` you can extract `2 + 3`, even
|
||||||
|
though it is not part of the expression.
|
||||||
|
"""
|
||||||
|
typ = parent_node.type
|
||||||
|
is_suite_part = typ in ('suite', 'file_input')
|
||||||
|
if typ in EXPRESSION_PARTS or is_suite_part:
|
||||||
|
nodes = parent_node.children
|
||||||
|
for i, n in enumerate(nodes):
|
||||||
|
if n.end_pos > pos:
|
||||||
|
start_index = i
|
||||||
|
if n.type == 'operator':
|
||||||
|
start_index -= 1
|
||||||
|
break
|
||||||
|
for i, n in reversed(list(enumerate(nodes))):
|
||||||
|
if n.start_pos < until_pos:
|
||||||
|
end_index = i
|
||||||
|
if n.type == 'operator':
|
||||||
|
end_index += 1
|
||||||
|
|
||||||
|
# Something like `not foo or bar` should not be cut after not
|
||||||
|
for n2 in nodes[i:]:
|
||||||
|
if _is_not_extractable_syntax(n2):
|
||||||
|
end_index += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
break
|
||||||
|
nodes = nodes[start_index:end_index + 1]
|
||||||
|
if not is_suite_part:
|
||||||
|
nodes[0:1] = _remove_unwanted_expression_nodes(nodes[0], pos, until_pos)
|
||||||
|
nodes[-1:] = _remove_unwanted_expression_nodes(nodes[-1], pos, until_pos)
|
||||||
|
return nodes
|
||||||
|
return [parent_node]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_not_extractable_syntax(node):
|
||||||
|
return node.type == 'operator' \
|
||||||
|
or node.type == 'keyword' and node.value not in ('None', 'True', 'False')
|
||||||
|
|
||||||
|
|
||||||
|
def extract_function(inference_state, path, module_context, name, pos, until_pos):
|
||||||
|
nodes = _find_nodes(module_context.tree_node, pos, until_pos)
|
||||||
|
assert len(nodes)
|
||||||
|
|
||||||
|
is_expression, _ = _is_expression_with_error(nodes)
|
||||||
|
context = module_context.create_context(nodes[0])
|
||||||
|
is_bound_method = context.is_bound_method()
|
||||||
|
params, return_variables = list(_find_inputs_and_outputs(module_context, context, nodes))
|
||||||
|
|
||||||
|
# Find variables
|
||||||
|
# Is a class method / method
|
||||||
|
if context.is_module():
|
||||||
|
insert_before_leaf = None # Leaf will be determined later
|
||||||
|
else:
|
||||||
|
node = _get_code_insertion_node(context.tree_node, is_bound_method)
|
||||||
|
insert_before_leaf = node.get_first_leaf()
|
||||||
|
if is_expression:
|
||||||
|
code_block = 'return ' + _expression_nodes_to_string(nodes) + '\n'
|
||||||
|
remaining_prefix = None
|
||||||
|
has_ending_return_stmt = False
|
||||||
|
else:
|
||||||
|
has_ending_return_stmt = _is_node_ending_return_stmt(nodes[-1])
|
||||||
|
if not has_ending_return_stmt:
|
||||||
|
# Find the actually used variables (of the defined ones). If none are
|
||||||
|
# used (e.g. if the range covers the whole function), return the last
|
||||||
|
# defined variable.
|
||||||
|
return_variables = list(_find_needed_output_variables(
|
||||||
|
context,
|
||||||
|
nodes[0].parent,
|
||||||
|
nodes[-1].end_pos,
|
||||||
|
return_variables
|
||||||
|
)) or [return_variables[-1]] if return_variables else []
|
||||||
|
|
||||||
|
remaining_prefix, code_block = _suite_nodes_to_string(nodes, pos)
|
||||||
|
after_leaf = nodes[-1].get_next_leaf()
|
||||||
|
first, second = _split_prefix_at(after_leaf, until_pos[0])
|
||||||
|
code_block += first
|
||||||
|
|
||||||
|
code_block = dedent(code_block)
|
||||||
|
if not has_ending_return_stmt:
|
||||||
|
output_var_str = ', '.join(return_variables)
|
||||||
|
code_block += 'return ' + output_var_str + '\n'
|
||||||
|
|
||||||
|
# Check if we have to raise RefactoringError
|
||||||
|
_check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)
|
||||||
|
|
||||||
|
decorator = ''
|
||||||
|
self_param = None
|
||||||
|
if is_bound_method:
|
||||||
|
if not function_is_staticmethod(context.tree_node):
|
||||||
|
function_param_names = context.get_value().get_param_names()
|
||||||
|
if len(function_param_names):
|
||||||
|
self_param = function_param_names[0].string_name
|
||||||
|
params = [p for p in params if p != self_param]
|
||||||
|
|
||||||
|
if function_is_classmethod(context.tree_node):
|
||||||
|
decorator = '@classmethod\n'
|
||||||
|
else:
|
||||||
|
code_block += '\n'
|
||||||
|
|
||||||
|
function_code = '%sdef %s(%s):\n%s' % (
|
||||||
|
decorator,
|
||||||
|
name,
|
||||||
|
', '.join(params if self_param is None else [self_param] + params),
|
||||||
|
indent_block(code_block)
|
||||||
|
)
|
||||||
|
|
||||||
|
function_call = '%s(%s)' % (
|
||||||
|
('' if self_param is None else self_param + '.') + name,
|
||||||
|
', '.join(params)
|
||||||
|
)
|
||||||
|
if is_expression:
|
||||||
|
replacement = function_call
|
||||||
|
else:
|
||||||
|
if has_ending_return_stmt:
|
||||||
|
replacement = 'return ' + function_call + '\n'
|
||||||
|
else:
|
||||||
|
replacement = output_var_str + ' = ' + function_call + '\n'
|
||||||
|
|
||||||
|
replacement_dct = _replace(nodes, replacement, function_code, pos,
|
||||||
|
insert_before_leaf, remaining_prefix)
|
||||||
|
if not is_expression:
|
||||||
|
replacement_dct[after_leaf] = second + after_leaf.value
|
||||||
|
file_to_node_changes = {path: replacement_dct}
|
||||||
|
return Refactoring(inference_state, file_to_node_changes)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_for_non_extractables(nodes):
|
||||||
|
for n in nodes:
|
||||||
|
try:
|
||||||
|
children = n.children
|
||||||
|
except AttributeError:
|
||||||
|
if n.value == 'return':
|
||||||
|
raise RefactoringError(
|
||||||
|
'Can only extract return statements if they are at the end.')
|
||||||
|
if n.value == 'yield':
|
||||||
|
raise RefactoringError('Cannot extract yield statements.')
|
||||||
|
else:
|
||||||
|
_check_for_non_extractables(children)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_name_input(module_context, names, first, last):
|
||||||
|
for name in names:
|
||||||
|
if name.api_type == 'param' or not name.parent_context.is_module():
|
||||||
|
if name.get_root_context() is not module_context:
|
||||||
|
return True
|
||||||
|
if name.start_pos is None or not (first <= name.start_pos < last):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _find_inputs_and_outputs(module_context, context, nodes):
|
||||||
|
first = nodes[0].start_pos
|
||||||
|
last = nodes[-1].end_pos
|
||||||
|
|
||||||
|
inputs = []
|
||||||
|
outputs = []
|
||||||
|
for name in _find_non_global_names(nodes):
|
||||||
|
if name.is_definition():
|
||||||
|
if name not in outputs:
|
||||||
|
outputs.append(name.value)
|
||||||
|
else:
|
||||||
|
if name.value not in inputs:
|
||||||
|
name_definitions = context.goto(name, name.start_pos)
|
||||||
|
if not name_definitions \
|
||||||
|
or _is_name_input(module_context, name_definitions, first, last):
|
||||||
|
inputs.append(name.value)
|
||||||
|
|
||||||
|
# Check if outputs are really needed:
|
||||||
|
return inputs, outputs
|
||||||
|
|
||||||
|
|
||||||
|
def _find_non_global_names(nodes):
|
||||||
|
for node in nodes:
|
||||||
|
try:
|
||||||
|
children = node.children
|
||||||
|
except AttributeError:
|
||||||
|
if node.type == 'name':
|
||||||
|
yield node
|
||||||
|
else:
|
||||||
|
# We only want to check foo in foo.bar
|
||||||
|
if node.type == 'trailer' and node.children[0] == '.':
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield from _find_non_global_names(children)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_code_insertion_node(node, is_bound_method):
|
||||||
|
if not is_bound_method or function_is_staticmethod(node):
|
||||||
|
while node.parent.type != 'file_input':
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
while node.parent.type in ('async_funcdef', 'decorated', 'async_stmt'):
|
||||||
|
node = node.parent
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def _find_needed_output_variables(context, search_node, at_least_pos, return_variables):
|
||||||
|
"""
|
||||||
|
Searches everything after at_least_pos in a node and checks if any of the
|
||||||
|
return_variables are used in there and returns those.
|
||||||
|
"""
|
||||||
|
for node in search_node.children:
|
||||||
|
if node.start_pos < at_least_pos:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return_variables = set(return_variables)
|
||||||
|
for name in _find_non_global_names([node]):
|
||||||
|
if not name.is_definition() and name.value in return_variables:
|
||||||
|
return_variables.remove(name.value)
|
||||||
|
yield name.value
|
||||||
|
|
||||||
|
|
||||||
|
def _is_node_ending_return_stmt(node):
|
||||||
|
t = node.type
|
||||||
|
if t == 'simple_stmt':
|
||||||
|
return _is_node_ending_return_stmt(node.children[0])
|
||||||
|
return t == 'return_stmt'
|
||||||
@@ -9,7 +9,7 @@ just use IPython instead::
|
|||||||
Then you will be able to use Jedi completer in your Python interpreter::
|
Then you will be able to use Jedi completer in your Python interpreter::
|
||||||
|
|
||||||
$ python
|
$ python
|
||||||
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
|
Python 3.9.2+ (default, Jul 20 2020, 22:15:08)
|
||||||
[GCC 4.6.1] on linux2
|
[GCC 4.6.1] on linux2
|
||||||
Type "help", "copyright", "credits" or "license" for more information.
|
Type "help", "copyright", "credits" or "license" for more information.
|
||||||
>>> import os
|
>>> import os
|
||||||
|
|||||||
+6
-8
@@ -9,7 +9,6 @@ names in a module, but pretty much an arbitrary string.
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from jedi._compatibility import unicode
|
|
||||||
from jedi.inference.names import AbstractArbitraryName
|
from jedi.inference.names import AbstractArbitraryName
|
||||||
from jedi.inference.helpers import infer_call_of_leaf
|
from jedi.inference.helpers import infer_call_of_leaf
|
||||||
from jedi.api.classes import Completion
|
from jedi.api.classes import Completion
|
||||||
@@ -19,7 +18,7 @@ _sentinel = object()
|
|||||||
|
|
||||||
|
|
||||||
class StringName(AbstractArbitraryName):
|
class StringName(AbstractArbitraryName):
|
||||||
api_type = u'string'
|
api_type = 'string'
|
||||||
is_value_name = False
|
is_value_name = False
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote
|
|||||||
|
|
||||||
|
|
||||||
def _create_repr_string(literal_string, dict_key):
|
def _create_repr_string(literal_string, dict_key):
|
||||||
if not isinstance(dict_key, (unicode, bytes)) or not literal_string:
|
if not isinstance(dict_key, (str, bytes)) or not literal_string:
|
||||||
return repr(dict_key)
|
return repr(dict_key)
|
||||||
|
|
||||||
r = repr(dict_key)
|
r = repr(dict_key)
|
||||||
@@ -93,17 +92,16 @@ def _get_string_prefix_and_quote(string):
|
|||||||
return match.group(1), match.group(2)
|
return match.group(1), match.group(2)
|
||||||
|
|
||||||
|
|
||||||
def _get_string_quote(string):
|
|
||||||
return _get_string_prefix_and_quote(string)[1]
|
|
||||||
|
|
||||||
|
|
||||||
def _matches_quote_at_position(code_lines, quote, position):
|
def _matches_quote_at_position(code_lines, quote, position):
|
||||||
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
||||||
return string == quote
|
return string == quote
|
||||||
|
|
||||||
|
|
||||||
def get_quote_ending(string, code_lines, position, invert_result=False):
|
def get_quote_ending(string, code_lines, position, invert_result=False):
|
||||||
quote = _get_string_quote(string)
|
_, quote = _get_string_prefix_and_quote(string)
|
||||||
|
if quote is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
# Add a quote only if it's not already there.
|
# Add a quote only if it's not already there.
|
||||||
if _matches_quote_at_position(code_lines, quote, position) != invert_result:
|
if _matches_quote_at_position(code_lines, quote, position) != invert_result:
|
||||||
return ''
|
return ''
|
||||||
|
|||||||
+3
-2
@@ -13,14 +13,15 @@ these variables are being cleaned after every API usage.
|
|||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from parso.cache import parser_cache
|
from parso.cache import parser_cache
|
||||||
|
|
||||||
_time_caches = {}
|
_time_caches: Dict[str, Dict[Any, Tuple[float, Any]]] = {}
|
||||||
|
|
||||||
|
|
||||||
def clear_time_caches(delete_all=False):
|
def clear_time_caches(delete_all: bool = False) -> None:
|
||||||
""" Jedi caches many things, that should be completed after each completion
|
""" Jedi caches many things, that should be completed after each completion
|
||||||
finishes.
|
finishes.
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import os
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
def traverse_parents(path, include_current=False):
|
|
||||||
if not include_current:
|
|
||||||
path = os.path.dirname(path)
|
|
||||||
|
|
||||||
previous = None
|
|
||||||
while previous != path:
|
|
||||||
yield path
|
|
||||||
previous = path
|
|
||||||
path = os.path.dirname(path)
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def monkeypatch(obj, attribute_name, new_value):
|
def monkeypatch(obj, attribute_name, new_value):
|
||||||
"""
|
"""
|
||||||
@@ -24,3 +12,13 @@ def monkeypatch(obj, attribute_name, new_value):
|
|||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
setattr(obj, attribute_name, old_value)
|
setattr(obj, attribute_name, old_value)
|
||||||
|
|
||||||
|
|
||||||
|
def indent_block(text, indention=' '):
|
||||||
|
"""This function indents a text block with a default of four spaces."""
|
||||||
|
temp = ''
|
||||||
|
while text and text[-1] == '\n':
|
||||||
|
temp += text[-1]
|
||||||
|
text = text[:-1]
|
||||||
|
lines = text.split('\n')
|
||||||
|
return '\n'.join(map(lambda s: indention + s, lines)) + temp
|
||||||
@@ -1 +0,0 @@
|
|||||||
from jedi.common.value import BaseValueSet, BaseValue
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
class BaseValue(object):
|
|
||||||
def __init__(self, inference_state, parent_context=None):
|
|
||||||
self.inference_state = inference_state
|
|
||||||
self.parent_context = parent_context
|
|
||||||
|
|
||||||
def get_root_context(self):
|
|
||||||
value = self
|
|
||||||
while True:
|
|
||||||
if value.parent_context is None:
|
|
||||||
return value
|
|
||||||
value = value.parent_context
|
|
||||||
|
|
||||||
|
|
||||||
class BaseValueSet(object):
|
|
||||||
def __init__(self, iterable):
|
|
||||||
self._set = frozenset(iterable)
|
|
||||||
for value in iterable:
|
|
||||||
assert not isinstance(value, BaseValueSet)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_frozen_set(cls, frozenset_):
|
|
||||||
self = cls.__new__(cls)
|
|
||||||
self._set = frozenset_
|
|
||||||
return self
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_sets(cls, sets):
|
|
||||||
"""
|
|
||||||
Used to work with an iterable of set.
|
|
||||||
"""
|
|
||||||
aggregated = set()
|
|
||||||
for set_ in sets:
|
|
||||||
if isinstance(set_, BaseValueSet):
|
|
||||||
aggregated |= set_._set
|
|
||||||
else:
|
|
||||||
aggregated |= frozenset(set_)
|
|
||||||
return cls._from_frozen_set(frozenset(aggregated))
|
|
||||||
|
|
||||||
def __or__(self, other):
|
|
||||||
return self._from_frozen_set(self._set | other._set)
|
|
||||||
|
|
||||||
def __and__(self, other):
|
|
||||||
return self._from_frozen_set(self._set & other._set)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for element in self._set:
|
|
||||||
yield element
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self._set)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._set)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'S{%s}' % (', '.join(str(s) for s in self._set))
|
|
||||||
|
|
||||||
def filter(self, filter_func):
|
|
||||||
return self.__class__(filter(filter_func, self._set))
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
def mapper(*args, **kwargs):
|
|
||||||
return self.from_sets(
|
|
||||||
getattr(value, name)(*args, **kwargs)
|
|
||||||
for value in self._set
|
|
||||||
)
|
|
||||||
return mapper
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self._set == other._set
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self._set)
|
|
||||||
+10
-18
@@ -1,8 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from typing import Callable, Optional
|
||||||
from jedi._compatibility import encoding, is_py3, u
|
|
||||||
|
|
||||||
_inited = False
|
_inited = False
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ try:
|
|||||||
raise ImportError
|
raise ImportError
|
||||||
else:
|
else:
|
||||||
# Use colorama for nicer console output.
|
# Use colorama for nicer console output.
|
||||||
from colorama import Fore, init
|
from colorama import Fore, init # type: ignore[import]
|
||||||
from colorama import initialise
|
from colorama import initialise
|
||||||
|
|
||||||
def _lazy_colorama_init(): # noqa: F811
|
def _lazy_colorama_init(): # noqa: F811
|
||||||
@@ -47,7 +46,7 @@ try:
|
|||||||
_inited = True
|
_inited = True
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
class Fore(object):
|
class Fore: # type: ignore[no-redef]
|
||||||
RED = ''
|
RED = ''
|
||||||
GREEN = ''
|
GREEN = ''
|
||||||
YELLOW = ''
|
YELLOW = ''
|
||||||
@@ -64,7 +63,7 @@ enable_warning = False
|
|||||||
enable_notice = False
|
enable_notice = False
|
||||||
|
|
||||||
# callback, interface: level, str
|
# callback, interface: level, str
|
||||||
debug_function = None
|
debug_function: Optional[Callable[[str, str], None]] = None
|
||||||
_debug_indent = 0
|
_debug_indent = 0
|
||||||
_start_time = time.time()
|
_start_time = time.time()
|
||||||
|
|
||||||
@@ -84,29 +83,27 @@ def increase_indent(func):
|
|||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def increase_indent_cm(title=None):
|
def increase_indent_cm(title=None, color='MAGENTA'):
|
||||||
global _debug_indent
|
global _debug_indent
|
||||||
if title:
|
if title:
|
||||||
dbg('Start: ' + title, color='MAGENTA')
|
dbg('Start: ' + title, color=color)
|
||||||
_debug_indent += 1
|
_debug_indent += 1
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
_debug_indent -= 1
|
_debug_indent -= 1
|
||||||
if title:
|
if title:
|
||||||
dbg('End: ' + title, color='MAGENTA')
|
dbg('End: ' + title, color=color)
|
||||||
|
|
||||||
|
|
||||||
def dbg(message, *args, **kwargs):
|
def dbg(message, *args, color='GREEN'):
|
||||||
""" Looks at the stack, to see if a debug message should be printed. """
|
""" Looks at the stack, to see if a debug message should be printed. """
|
||||||
# Python 2 compatibility, because it doesn't understand default args
|
|
||||||
color = kwargs.pop('color', 'GREEN')
|
|
||||||
assert color
|
assert color
|
||||||
|
|
||||||
if debug_function and enable_notice:
|
if debug_function and enable_notice:
|
||||||
i = ' ' * _debug_indent
|
i = ' ' * _debug_indent
|
||||||
_lazy_colorama_init()
|
_lazy_colorama_init()
|
||||||
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args))
|
debug_function(color, i + 'dbg: ' + message % tuple(repr(a) for a in args))
|
||||||
|
|
||||||
|
|
||||||
def warning(message, *args, **kwargs):
|
def warning(message, *args, **kwargs):
|
||||||
@@ -116,7 +113,7 @@ def warning(message, *args, **kwargs):
|
|||||||
if debug_function and enable_warning:
|
if debug_function and enable_warning:
|
||||||
i = ' ' * _debug_indent
|
i = ' ' * _debug_indent
|
||||||
if format:
|
if format:
|
||||||
message = message % tuple(u(repr(a)) for a in args)
|
message = message % tuple(repr(a) for a in args)
|
||||||
debug_function('RED', i + 'warning: ' + message)
|
debug_function('RED', i + 'warning: ' + message)
|
||||||
|
|
||||||
|
|
||||||
@@ -135,9 +132,4 @@ def print_to_stdout(color, str_out):
|
|||||||
"""
|
"""
|
||||||
col = getattr(Fore, color)
|
col = getattr(Fore, color)
|
||||||
_lazy_colorama_init()
|
_lazy_colorama_init()
|
||||||
if not is_py3:
|
|
||||||
str_out = str_out.encode(encoding, 'replace')
|
|
||||||
print(col + str_out + Fore.RESET)
|
print(col + str_out + Fore.RESET)
|
||||||
|
|
||||||
|
|
||||||
# debug_function = print_to_stdout
|
|
||||||
+7
-4
@@ -3,10 +3,13 @@ import os
|
|||||||
from parso import file_io
|
from parso import file_io
|
||||||
|
|
||||||
|
|
||||||
class AbstractFolderIO(object):
|
class AbstractFolderIO:
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
|
def get_base_name(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -54,7 +57,7 @@ class FolderIO(AbstractFolderIO):
|
|||||||
del dirs[i]
|
del dirs[i]
|
||||||
|
|
||||||
|
|
||||||
class FileIOFolderMixin(object):
|
class FileIOFolderMixin:
|
||||||
def get_parent_folder(self):
|
def get_parent_folder(self):
|
||||||
return FolderIO(os.path.dirname(self.path))
|
return FolderIO(os.path.dirname(self.path))
|
||||||
|
|
||||||
@@ -62,13 +65,13 @@ class FileIOFolderMixin(object):
|
|||||||
class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
|
class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
|
||||||
"""For .zip and .egg archives"""
|
"""For .zip and .egg archives"""
|
||||||
def __init__(self, path, code, zip_path):
|
def __init__(self, path, code, zip_path):
|
||||||
super(ZipFileIO, self).__init__(path, code)
|
super().__init__(path, code)
|
||||||
self._zip_path = zip_path
|
self._zip_path = zip_path
|
||||||
|
|
||||||
def get_last_modified(self):
|
def get_last_modified(self):
|
||||||
try:
|
try:
|
||||||
return os.path.getmtime(self._zip_path)
|
return os.path.getmtime(self._zip_path)
|
||||||
except OSError: # Python 3 would probably only need FileNotFoundError
|
except (FileNotFoundError, PermissionError, NotADirectoryError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-11
@@ -63,7 +63,6 @@ only *inferes* what needs to be *inferred*. All the statements and modules
|
|||||||
that are not used are just being ignored.
|
that are not used are just being ignored.
|
||||||
"""
|
"""
|
||||||
import parso
|
import parso
|
||||||
from parso import python_bytes_to_unicode
|
|
||||||
from jedi.file_io import FileIO
|
from jedi.file_io import FileIO
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
@@ -82,7 +81,7 @@ from jedi.inference.imports import follow_error_node_imports_if_possible
|
|||||||
from jedi.plugins import plugin_manager
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
class InferenceState(object):
|
class InferenceState:
|
||||||
def __init__(self, project, environment=None, script_path=None):
|
def __init__(self, project, environment=None, script_path=None):
|
||||||
if environment is None:
|
if environment is None:
|
||||||
environment = project.get_environment()
|
environment = project.get_environment()
|
||||||
@@ -121,19 +120,18 @@ class InferenceState(object):
|
|||||||
debug.dbg('execute result: %s in %s', value_set, value)
|
debug.dbg('execute result: %s in %s', value_set, value)
|
||||||
return value_set
|
return value_set
|
||||||
|
|
||||||
@property
|
# mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
|
||||||
|
@property # type: ignore[misc]
|
||||||
@inference_state_function_cache()
|
@inference_state_function_cache()
|
||||||
def builtins_module(self):
|
def builtins_module(self):
|
||||||
module_name = u'builtins'
|
module_name = 'builtins'
|
||||||
if self.environment.version_info.major == 2:
|
|
||||||
module_name = u'__builtin__'
|
|
||||||
builtins_module, = self.import_module((module_name,), sys_path=())
|
builtins_module, = self.import_module((module_name,), sys_path=())
|
||||||
return builtins_module
|
return builtins_module
|
||||||
|
|
||||||
@property
|
@property # type: ignore[misc]
|
||||||
@inference_state_function_cache()
|
@inference_state_function_cache()
|
||||||
def typing_module(self):
|
def typing_module(self):
|
||||||
typing_module, = self.import_module((u'typing',))
|
typing_module, = self.import_module(('typing',))
|
||||||
return typing_module
|
return typing_module
|
||||||
|
|
||||||
def reset_recursion_limitations(self):
|
def reset_recursion_limitations(self):
|
||||||
@@ -142,7 +140,7 @@ class InferenceState(object):
|
|||||||
|
|
||||||
def get_sys_path(self, **kwargs):
|
def get_sys_path(self, **kwargs):
|
||||||
"""Convenience function"""
|
"""Convenience function"""
|
||||||
return self.project._get_sys_path(self, environment=self.environment, **kwargs)
|
return self.project._get_sys_path(self, **kwargs)
|
||||||
|
|
||||||
def infer(self, context, name):
|
def infer(self, context, name):
|
||||||
def_ = name.get_definition(import_name_always=True)
|
def_ = name.get_definition(import_name_always=True)
|
||||||
@@ -172,6 +170,8 @@ class InferenceState(object):
|
|||||||
return tree_name_to_values(self, context, name)
|
return tree_name_to_values(self, context, name)
|
||||||
elif type_ == 'param':
|
elif type_ == 'param':
|
||||||
return context.py__getattribute__(name.value, position=name.end_pos)
|
return context.py__getattribute__(name.value, position=name.end_pos)
|
||||||
|
elif type_ == 'namedexpr_test':
|
||||||
|
return context.infer_node(def_)
|
||||||
else:
|
else:
|
||||||
result = follow_error_node_imports_if_possible(context, name)
|
result = follow_error_node_imports_if_possible(context, name)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
@@ -179,14 +179,16 @@ class InferenceState(object):
|
|||||||
|
|
||||||
return helpers.infer_call_of_leaf(context, name)
|
return helpers.infer_call_of_leaf(context, name)
|
||||||
|
|
||||||
def parse_and_get_code(self, code=None, path=None, encoding='utf-8',
|
def parse_and_get_code(self, code=None, path=None,
|
||||||
use_latest_grammar=False, file_io=None, **kwargs):
|
use_latest_grammar=False, file_io=None, **kwargs):
|
||||||
|
if path is not None:
|
||||||
|
path = str(path)
|
||||||
if code is None:
|
if code is None:
|
||||||
if file_io is None:
|
if file_io is None:
|
||||||
file_io = FileIO(path)
|
file_io = FileIO(path)
|
||||||
code = file_io.read()
|
code = file_io.read()
|
||||||
# We cannot just use parso, because it doesn't use errors='replace'.
|
# We cannot just use parso, because it doesn't use errors='replace'.
|
||||||
code = python_bytes_to_unicode(code, encoding=encoding, errors='replace')
|
code = parso.python_bytes_to_unicode(code, encoding='utf-8', errors='replace')
|
||||||
|
|
||||||
if len(code) > settings._cropped_file_size:
|
if len(code) > settings._cropped_file_size:
|
||||||
code = code[:settings._cropped_file_size]
|
code = code[:settings._cropped_file_size]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ Module for statical analysis.
|
|||||||
"""
|
"""
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
|
|
||||||
from jedi._compatibility import force_unicode
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.helpers import is_string
|
from jedi.inference.helpers import is_string
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ CODES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Error(object):
|
class Error:
|
||||||
def __init__(self, name, module_path, start_pos, message=None):
|
def __init__(self, name, module_path, start_pos, message=None):
|
||||||
self.path = module_path
|
self.path = module_path
|
||||||
self._start_pos = start_pos
|
self._start_pos = start_pos
|
||||||
@@ -50,13 +49,10 @@ class Error(object):
|
|||||||
first = self.__class__.__name__[0]
|
first = self.__class__.__name__[0]
|
||||||
return first + str(CODES[self.name][0])
|
return first + str(CODES[self.name][0])
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
|
return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
|
||||||
self.code, self.message)
|
self.code, self.message)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.__unicode__()
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (self.path == other.path and self.name == other.name
|
return (self.path == other.path and self.name == other.name
|
||||||
and self._start_pos == other._start_pos)
|
and self._start_pos == other._start_pos)
|
||||||
@@ -193,7 +189,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None)
|
|||||||
key, lazy_value = unpacked_args[1]
|
key, lazy_value = unpacked_args[1]
|
||||||
names = list(lazy_value.infer())
|
names = list(lazy_value.infer())
|
||||||
assert len(names) == 1 and is_string(names[0])
|
assert len(names) == 1 and is_string(names[0])
|
||||||
assert force_unicode(names[0].get_safe_value()) == payload[1].value
|
assert names[0].get_safe_value() == payload[1].value
|
||||||
|
|
||||||
# Check objects
|
# Check objects
|
||||||
key, lazy_value = unpacked_args[0]
|
key, lazy_value = unpacked_args[0]
|
||||||
|
|||||||
+21
-42
@@ -1,8 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
|
from itertools import zip_longest
|
||||||
|
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
|
|
||||||
from jedi._compatibility import zip_longest
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.utils import PushBackIterator
|
from jedi.inference.utils import PushBackIterator
|
||||||
from jedi.inference import analysis
|
from jedi.inference import analysis
|
||||||
@@ -35,7 +35,7 @@ class ParamIssue(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callback_param=False):
|
def repack_with_argument_clinic(clinic_string):
|
||||||
"""
|
"""
|
||||||
Transforms a function or method with arguments to the signature that is
|
Transforms a function or method with arguments to the signature that is
|
||||||
given as an argument clinic notation.
|
given as an argument clinic notation.
|
||||||
@@ -46,35 +46,29 @@ def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callbac
|
|||||||
str.split.__text_signature__
|
str.split.__text_signature__
|
||||||
# Results in: '($self, /, sep=None, maxsplit=-1)'
|
# Results in: '($self, /, sep=None, maxsplit=-1)'
|
||||||
"""
|
"""
|
||||||
clinic_args = list(_parse_argument_clinic(string))
|
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
def wrapper(context, *args, **kwargs):
|
def wrapper(value, arguments):
|
||||||
if keep_arguments_param:
|
|
||||||
arguments = kwargs['arguments']
|
|
||||||
else:
|
|
||||||
arguments = kwargs.pop('arguments')
|
|
||||||
if not keep_arguments_param:
|
|
||||||
kwargs.pop('callback', None)
|
|
||||||
try:
|
try:
|
||||||
args += tuple(_iterate_argument_clinic(
|
args = tuple(iterate_argument_clinic(
|
||||||
context.inference_state,
|
value.inference_state,
|
||||||
arguments,
|
arguments,
|
||||||
clinic_args
|
clinic_string,
|
||||||
))
|
))
|
||||||
except ParamIssue:
|
except ParamIssue:
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
else:
|
else:
|
||||||
return func(context, *args, **kwargs)
|
return func(value, *args)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def _iterate_argument_clinic(inference_state, arguments, parameters):
|
def iterate_argument_clinic(inference_state, arguments, clinic_string):
|
||||||
"""Uses a list with argument clinic information (see PEP 436)."""
|
"""Uses a list with argument clinic information (see PEP 436)."""
|
||||||
|
clinic_args = list(_parse_argument_clinic(clinic_string))
|
||||||
|
|
||||||
iterator = PushBackIterator(arguments.unpack())
|
iterator = PushBackIterator(arguments.unpack())
|
||||||
for i, (name, optional, allow_kwargs, stars) in enumerate(parameters):
|
for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
|
||||||
if stars == 1:
|
if stars == 1:
|
||||||
lazy_values = []
|
lazy_values = []
|
||||||
for key, argument in iterator:
|
for key, argument in iterator:
|
||||||
@@ -94,7 +88,7 @@ def _iterate_argument_clinic(inference_state, arguments, parameters):
|
|||||||
raise ParamIssue
|
raise ParamIssue
|
||||||
if argument is None and not optional:
|
if argument is None and not optional:
|
||||||
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
||||||
name, len(parameters), i)
|
name, len(clinic_args), i)
|
||||||
raise ParamIssue
|
raise ParamIssue
|
||||||
|
|
||||||
value_set = NO_VALUES if argument is None else argument.infer()
|
value_set = NO_VALUES if argument is None else argument.infer()
|
||||||
@@ -130,16 +124,7 @@ def _parse_argument_clinic(string):
|
|||||||
allow_kwargs = True
|
allow_kwargs = True
|
||||||
|
|
||||||
|
|
||||||
class _AbstractArgumentsMixin(object):
|
class _AbstractArgumentsMixin:
|
||||||
def infer_all(self, funcdef=None):
|
|
||||||
"""
|
|
||||||
Inferes all arguments as a support for static analysis
|
|
||||||
(normally Jedi).
|
|
||||||
"""
|
|
||||||
for key, lazy_value in self.unpack():
|
|
||||||
types = lazy_value.infer()
|
|
||||||
try_iter_content(types)
|
|
||||||
|
|
||||||
def unpack(self, funcdef=None):
|
def unpack(self, funcdef=None):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -157,11 +142,8 @@ def unpack_arglist(arglist):
|
|||||||
if arglist is None:
|
if arglist is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Allow testlist here as well for Python2's class inheritance
|
if arglist.type != 'arglist' and not (
|
||||||
# definitions.
|
arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
|
||||||
if not (arglist.type in ('arglist', 'testlist') or (
|
|
||||||
# in python 3.5 **arg is an argument, not arglist
|
|
||||||
arglist.type == 'argument' and arglist.children[0] in ('*', '**'))):
|
|
||||||
yield 0, arglist
|
yield 0, arglist
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -170,7 +152,9 @@ def unpack_arglist(arglist):
|
|||||||
if child == ',':
|
if child == ',':
|
||||||
continue
|
continue
|
||||||
elif child in ('*', '**'):
|
elif child in ('*', '**'):
|
||||||
yield len(child.value), next(iterator)
|
c = next(iterator, None)
|
||||||
|
assert c is not None
|
||||||
|
yield len(child.value), c
|
||||||
elif child.type == 'argument' and \
|
elif child.type == 'argument' and \
|
||||||
child.children[0] in ('*', '**'):
|
child.children[0] in ('*', '**'):
|
||||||
assert len(child.children) == 2
|
assert len(child.children) == 2
|
||||||
@@ -202,16 +186,13 @@ class TreeArguments(AbstractArguments):
|
|||||||
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
||||||
for a in arrays]
|
for a in arrays]
|
||||||
for values in list(zip_longest(*iterators)):
|
for values in list(zip_longest(*iterators)):
|
||||||
# TODO zip_longest yields None, that means this would raise
|
|
||||||
# an exception?
|
|
||||||
yield None, get_merged_lazy_value(
|
yield None, get_merged_lazy_value(
|
||||||
[v for v in values if v is not None]
|
[v for v in values if v is not None]
|
||||||
)
|
)
|
||||||
elif star_count == 2:
|
elif star_count == 2:
|
||||||
arrays = self.context.infer_node(el)
|
arrays = self.context.infer_node(el)
|
||||||
for dct in arrays:
|
for dct in arrays:
|
||||||
for key, values in _star_star_dict(self.context, dct, el, funcdef):
|
yield from _star_star_dict(self.context, dct, el, funcdef)
|
||||||
yield key, values
|
|
||||||
else:
|
else:
|
||||||
if el.type == 'argument':
|
if el.type == 'argument':
|
||||||
c = el.children
|
c = el.children
|
||||||
@@ -234,8 +215,7 @@ class TreeArguments(AbstractArguments):
|
|||||||
|
|
||||||
# Reordering arguments is necessary, because star args sometimes appear
|
# Reordering arguments is necessary, because star args sometimes appear
|
||||||
# after named argument, but in the actual order it's prepended.
|
# after named argument, but in the actual order it's prepended.
|
||||||
for named_arg in named_args:
|
yield from named_args
|
||||||
yield named_arg
|
|
||||||
|
|
||||||
def _as_tree_tuple_objects(self):
|
def _as_tree_tuple_objects(self):
|
||||||
for star_count, argument in unpack_arglist(self.argument_node):
|
for star_count, argument in unpack_arglist(self.argument_node):
|
||||||
@@ -336,8 +316,7 @@ def _iterate_star_args(context, array, input_node, funcdef=None):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for lazy_value in iter_():
|
yield from iter_()
|
||||||
yield lazy_value
|
|
||||||
|
|
||||||
|
|
||||||
def _star_star_dict(context, array, input_node, funcdef):
|
def _star_star_dict(context, array, input_node, funcdef):
|
||||||
|
|||||||
+162
-33
@@ -8,12 +8,12 @@ just one.
|
|||||||
"""
|
"""
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from operator import add
|
from operator import add
|
||||||
|
from itertools import zip_longest
|
||||||
|
|
||||||
from parso.python.tree import Name
|
from parso.python.tree import Name
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi._compatibility import zip_longest, unicode
|
|
||||||
from jedi.parser_utils import clean_scope_docstring
|
from jedi.parser_utils import clean_scope_docstring
|
||||||
from jedi.common import BaseValueSet, BaseValue
|
|
||||||
from jedi.inference.helpers import SimpleGetItemNotFound
|
from jedi.inference.helpers import SimpleGetItemNotFound
|
||||||
from jedi.inference.utils import safe_property
|
from jedi.inference.utils import safe_property
|
||||||
from jedi.inference.cache import inference_state_as_method_param_cache
|
from jedi.inference.cache import inference_state_as_method_param_cache
|
||||||
@@ -22,7 +22,7 @@ from jedi.cache import memoize_method
|
|||||||
sentinel = object()
|
sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
class HelperValueMixin(object):
|
class HelperValueMixin:
|
||||||
def get_root_context(self):
|
def get_root_context(self):
|
||||||
value = self
|
value = self
|
||||||
if value.parent_context is None:
|
if value.parent_context is None:
|
||||||
@@ -33,11 +33,6 @@ class HelperValueMixin(object):
|
|||||||
return value
|
return value
|
||||||
value = value.parent_context
|
value = value.parent_context
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@inference_state_as_method_param_cache()
|
|
||||||
def create_cached(cls, *args, **kwargs):
|
|
||||||
return cls(*args, **kwargs)
|
|
||||||
|
|
||||||
def execute(self, arguments):
|
def execute(self, arguments):
|
||||||
return self.inference_state.execute(self, arguments=arguments)
|
return self.inference_state.execute(self, arguments=arguments)
|
||||||
|
|
||||||
@@ -60,18 +55,14 @@ class HelperValueMixin(object):
|
|||||||
|
|
||||||
def _get_value_filters(self, name_or_str):
|
def _get_value_filters(self, name_or_str):
|
||||||
origin_scope = name_or_str if isinstance(name_or_str, Name) else None
|
origin_scope = name_or_str if isinstance(name_or_str, Name) else None
|
||||||
for f in self.get_filters(origin_scope=origin_scope):
|
yield from self.get_filters(origin_scope=origin_scope)
|
||||||
yield f
|
|
||||||
# This covers the case where a stub files are incomplete.
|
# This covers the case where a stub files are incomplete.
|
||||||
if self.is_stub():
|
if self.is_stub():
|
||||||
from jedi.inference.gradual.conversion import convert_values
|
from jedi.inference.gradual.conversion import convert_values
|
||||||
for c in convert_values(ValueSet({self})):
|
for c in convert_values(ValueSet({self})):
|
||||||
for f in c.get_filters():
|
yield from c.get_filters()
|
||||||
yield f
|
|
||||||
|
|
||||||
def goto(self, name_or_str, name_context=None, analysis_errors=True):
|
def goto(self, name_or_str, name_context=None, analysis_errors=True):
|
||||||
if name_context is None:
|
|
||||||
name_context = self
|
|
||||||
from jedi.inference import finder
|
from jedi.inference import finder
|
||||||
filters = self._get_value_filters(name_or_str)
|
filters = self._get_value_filters(name_or_str)
|
||||||
names = finder.filter_name(filters, name_or_str)
|
names = finder.filter_name(filters, name_or_str)
|
||||||
@@ -100,11 +91,14 @@ class HelperValueMixin(object):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
def py__await__(self):
|
def py__await__(self):
|
||||||
await_value_set = self.py__getattribute__(u"__await__")
|
await_value_set = self.py__getattribute__("__await__")
|
||||||
if not await_value_set:
|
if not await_value_set:
|
||||||
debug.warning('Tried to run __await__ on value %s', self)
|
debug.warning('Tried to run __await__ on value %s', self)
|
||||||
return await_value_set.execute_with_values()
|
return await_value_set.execute_with_values()
|
||||||
|
|
||||||
|
def py__name__(self):
|
||||||
|
return self.name.string_name
|
||||||
|
|
||||||
def iterate(self, contextualized_node=None, is_async=False):
|
def iterate(self, contextualized_node=None, is_async=False):
|
||||||
debug.dbg('iterate %s', self)
|
debug.dbg('iterate %s', self)
|
||||||
if is_async:
|
if is_async:
|
||||||
@@ -117,15 +111,19 @@ class HelperValueMixin(object):
|
|||||||
.py__getattribute__('__anext__').execute_with_values()
|
.py__getattribute__('__anext__').execute_with_values()
|
||||||
.py__getattribute__('__await__').execute_with_values()
|
.py__getattribute__('__await__').execute_with_values()
|
||||||
.py__stop_iteration_returns()
|
.py__stop_iteration_returns()
|
||||||
) # noqa
|
) # noqa: E124
|
||||||
])
|
])
|
||||||
return self.py__iter__(contextualized_node)
|
return self.py__iter__(contextualized_node)
|
||||||
|
|
||||||
def is_sub_class_of(self, class_value):
|
def is_sub_class_of(self, class_value):
|
||||||
for cls in self.py__mro__():
|
with debug.increase_indent_cm('subclass matching of %s <=> %s' % (self, class_value),
|
||||||
if cls.is_same_class(class_value):
|
color='BLUE'):
|
||||||
return True
|
for cls in self.py__mro__():
|
||||||
return False
|
if cls.is_same_class(class_value):
|
||||||
|
debug.dbg('matched subclass True', color='BLUE')
|
||||||
|
return True
|
||||||
|
debug.dbg('matched subclass False', color='BLUE')
|
||||||
|
return False
|
||||||
|
|
||||||
def is_same_class(self, class2):
|
def is_same_class(self, class2):
|
||||||
# Class matching should prefer comparisons that are not this function.
|
# Class matching should prefer comparisons that are not this function.
|
||||||
@@ -138,7 +136,7 @@ class HelperValueMixin(object):
|
|||||||
return self._as_context(*args, **kwargs)
|
return self._as_context(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Value(HelperValueMixin, BaseValue):
|
class Value(HelperValueMixin):
|
||||||
"""
|
"""
|
||||||
To be implemented by subclasses.
|
To be implemented by subclasses.
|
||||||
"""
|
"""
|
||||||
@@ -146,12 +144,11 @@ class Value(HelperValueMixin, BaseValue):
|
|||||||
# Possible values: None, tuple, list, dict and set. Here to deal with these
|
# Possible values: None, tuple, list, dict and set. Here to deal with these
|
||||||
# very important containers.
|
# very important containers.
|
||||||
array_type = None
|
array_type = None
|
||||||
|
api_type = 'not_defined_please_report_bug'
|
||||||
|
|
||||||
@property
|
def __init__(self, inference_state, parent_context=None):
|
||||||
def api_type(self):
|
self.inference_state = inference_state
|
||||||
# By default just lower name of the class. Can and should be
|
self.parent_context = parent_context
|
||||||
# overwritten.
|
|
||||||
return self.__class__.__name__.lower()
|
|
||||||
|
|
||||||
def py__getitem__(self, index_value_set, contextualized_node):
|
def py__getitem__(self, index_value_set, contextualized_node):
|
||||||
from jedi.inference import analysis
|
from jedi.inference import analysis
|
||||||
@@ -177,12 +174,18 @@ class Value(HelperValueMixin, BaseValue):
|
|||||||
message="TypeError: '%s' object is not iterable" % self)
|
message="TypeError: '%s' object is not iterable" % self)
|
||||||
return iter([])
|
return iter([])
|
||||||
|
|
||||||
|
def py__next__(self, contextualized_node=None):
|
||||||
|
return self.py__iter__(contextualized_node)
|
||||||
|
|
||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def is_class(self):
|
def is_class(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_class_mixin(self):
|
||||||
|
return False
|
||||||
|
|
||||||
def is_instance(self):
|
def is_instance(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -218,7 +221,6 @@ class Value(HelperValueMixin, BaseValue):
|
|||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
return clean_scope_docstring(self.tree_node)
|
return clean_scope_docstring(self.tree_node)
|
||||||
return None
|
|
||||||
|
|
||||||
def get_safe_value(self, default=sentinel):
|
def get_safe_value(self, default=sentinel):
|
||||||
if default is sentinel:
|
if default is sentinel:
|
||||||
@@ -247,6 +249,9 @@ class Value(HelperValueMixin, BaseValue):
|
|||||||
debug.warning("No __get__ defined on %s", self)
|
debug.warning("No __get__ defined on %s", self)
|
||||||
return ValueSet([self])
|
return ValueSet([self])
|
||||||
|
|
||||||
|
def py__get__on_class(self, calling_instance, instance, class_value):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
def get_qualified_names(self):
|
def get_qualified_names(self):
|
||||||
# Returns Optional[Tuple[str, ...]]
|
# Returns Optional[Tuple[str, ...]]
|
||||||
return None
|
return None
|
||||||
@@ -258,11 +263,41 @@ class Value(HelperValueMixin, BaseValue):
|
|||||||
def _as_context(self):
|
def _as_context(self):
|
||||||
raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
|
raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
|
||||||
|
|
||||||
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def py__name__(self):
|
def get_type_hint(self, add_class_info=True):
|
||||||
return self.name.string_name
|
return None
|
||||||
|
|
||||||
|
def infer_type_vars(self, value_set):
|
||||||
|
"""
|
||||||
|
When the current instance represents a type annotation, this method
|
||||||
|
tries to find information about undefined type vars and returns a dict
|
||||||
|
from type var name to value set.
|
||||||
|
|
||||||
|
This is for example important to understand what `iter([1])` returns.
|
||||||
|
According to typeshed, `iter` returns an `Iterator[_T]`:
|
||||||
|
|
||||||
|
def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
|
||||||
|
|
||||||
|
This functions would generate `int` for `_T` in this case, because it
|
||||||
|
unpacks the `Iterable`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
`self`: represents the annotation of the current parameter to infer the
|
||||||
|
value for. In the above example, this would initially be the
|
||||||
|
`Iterable[_T]` of the `iterable` parameter and then, when recursing,
|
||||||
|
just the `_T` generic parameter.
|
||||||
|
|
||||||
|
`value_set`: represents the actual argument passed to the parameter
|
||||||
|
we're inferrined for, or (for recursive calls) their types. In the
|
||||||
|
above example this would first be the representation of the list
|
||||||
|
`[1]` and then, when recursing, just of `1`.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def iterate_values(values, contextualized_node=None, is_async=False):
|
def iterate_values(values, contextualized_node=None, is_async=False):
|
||||||
@@ -321,14 +356,14 @@ class ValueWrapper(_ValueWrapperBase):
|
|||||||
|
|
||||||
class TreeValue(Value):
|
class TreeValue(Value):
|
||||||
def __init__(self, inference_state, parent_context, tree_node):
|
def __init__(self, inference_state, parent_context, tree_node):
|
||||||
super(TreeValue, self).__init__(inference_state, parent_context)
|
super().__init__(inference_state, parent_context)
|
||||||
self.tree_node = tree_node
|
self.tree_node = tree_node
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
|
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
|
||||||
|
|
||||||
|
|
||||||
class ContextualizedNode(object):
|
class ContextualizedNode:
|
||||||
def __init__(self, context, node):
|
def __init__(self, context, node):
|
||||||
self.context = context
|
self.context = context
|
||||||
self.node = node
|
self.node = node
|
||||||
@@ -349,7 +384,7 @@ def _getitem(value, index_values, contextualized_node):
|
|||||||
unused_values = set()
|
unused_values = set()
|
||||||
for index_value in index_values:
|
for index_value in index_values:
|
||||||
index = index_value.get_safe_value(default=None)
|
index = index_value.get_safe_value(default=None)
|
||||||
if type(index) in (float, int, str, unicode, slice, bytes):
|
if type(index) in (float, int, str, slice, bytes):
|
||||||
try:
|
try:
|
||||||
result |= value.py__simple_getitem__(index)
|
result |= value.py__simple_getitem__(index)
|
||||||
continue
|
continue
|
||||||
@@ -370,7 +405,69 @@ def _getitem(value, index_values, contextualized_node):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class ValueSet(BaseValueSet):
|
class ValueSet:
|
||||||
|
def __init__(self, iterable):
|
||||||
|
self._set = frozenset(iterable)
|
||||||
|
for value in iterable:
|
||||||
|
assert not isinstance(value, ValueSet)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_frozen_set(cls, frozenset_):
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._set = frozenset_
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_sets(cls, sets):
|
||||||
|
"""
|
||||||
|
Used to work with an iterable of set.
|
||||||
|
"""
|
||||||
|
aggregated = set()
|
||||||
|
for set_ in sets:
|
||||||
|
if isinstance(set_, ValueSet):
|
||||||
|
aggregated |= set_._set
|
||||||
|
else:
|
||||||
|
aggregated |= frozenset(set_)
|
||||||
|
return cls._from_frozen_set(frozenset(aggregated))
|
||||||
|
|
||||||
|
def __or__(self, other):
|
||||||
|
return self._from_frozen_set(self._set | other._set)
|
||||||
|
|
||||||
|
def __and__(self, other):
|
||||||
|
return self._from_frozen_set(self._set & other._set)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._set)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self._set)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._set)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'S{%s}' % (', '.join(str(s) for s in self._set))
|
||||||
|
|
||||||
|
def filter(self, filter_func):
|
||||||
|
return self.__class__(filter(filter_func, self._set))
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
def mapper(*args, **kwargs):
|
||||||
|
return self.from_sets(
|
||||||
|
getattr(value, name)(*args, **kwargs)
|
||||||
|
for value in self._set
|
||||||
|
)
|
||||||
|
return mapper
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self._set == other._set
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._set)
|
||||||
|
|
||||||
def py__class__(self):
|
def py__class__(self):
|
||||||
return ValueSet(c.py__class__() for c in self._set)
|
return ValueSet(c.py__class__() for c in self._set)
|
||||||
|
|
||||||
@@ -414,6 +511,38 @@ class ValueSet(BaseValueSet):
|
|||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
return [sig for c in self._set for sig in c.get_signatures()]
|
return [sig for c in self._set for sig in c.get_signatures()]
|
||||||
|
|
||||||
|
def get_type_hint(self, add_class_info=True):
|
||||||
|
t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
|
||||||
|
type_hints = sorted(filter(None, t))
|
||||||
|
if len(type_hints) == 1:
|
||||||
|
return type_hints[0]
|
||||||
|
|
||||||
|
optional = 'None' in type_hints
|
||||||
|
if optional:
|
||||||
|
type_hints.remove('None')
|
||||||
|
|
||||||
|
if len(type_hints) == 0:
|
||||||
|
return None
|
||||||
|
elif len(type_hints) == 1:
|
||||||
|
s = type_hints[0]
|
||||||
|
else:
|
||||||
|
s = 'Union[%s]' % ', '.join(type_hints)
|
||||||
|
if optional:
|
||||||
|
s = 'Optional[%s]' % s
|
||||||
|
return s
|
||||||
|
|
||||||
|
def infer_type_vars(self, value_set):
|
||||||
|
# Circular
|
||||||
|
from jedi.inference.gradual.annotation import merge_type_var_dicts
|
||||||
|
|
||||||
|
type_var_dict = {}
|
||||||
|
for value in self._set:
|
||||||
|
merge_type_var_dicts(
|
||||||
|
type_var_dict,
|
||||||
|
value.infer_type_vars(value_set),
|
||||||
|
)
|
||||||
|
return type_var_dict
|
||||||
|
|
||||||
|
|
||||||
NO_VALUES = ValueSet([])
|
NO_VALUES = ValueSet([])
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
default otherwise.
|
default otherwise.
|
||||||
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
|
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
|
||||||
"""
|
"""
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ class CachedMetaClass(type):
|
|||||||
"""
|
"""
|
||||||
@inference_state_as_method_param_cache()
|
@inference_state_as_method_param_cache()
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
return super(CachedMetaClass, self).__call__(*args, **kwargs)
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def inference_state_method_generator_cache():
|
def inference_state_method_generator_cache():
|
||||||
@@ -86,6 +87,7 @@ def inference_state_method_generator_cache():
|
|||||||
recursion errors and returns no further iterator elemends in that case.
|
recursion errors and returns no further iterator elemends in that case.
|
||||||
"""
|
"""
|
||||||
def func(function):
|
def func(function):
|
||||||
|
@wraps(function)
|
||||||
def wrapper(obj, *args, **kwargs):
|
def wrapper(obj, *args, **kwargs):
|
||||||
cache = obj.inference_state.memoize_cache
|
cache = obj.inference_state.memoize_cache
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from jedi._compatibility import unicode
|
# This file also re-exports symbols for wider use. We configure mypy and flake8
|
||||||
|
# to be aware that this file does this.
|
||||||
|
|
||||||
from jedi.inference.compiled.value import CompiledValue, CompiledName, \
|
from jedi.inference.compiled.value import CompiledValue, CompiledName, \
|
||||||
CompiledValueFilter, CompiledValueName, create_from_access_path
|
CompiledValueFilter, CompiledValueName, create_from_access_path
|
||||||
from jedi.inference.base_value import LazyValueWrapper
|
from jedi.inference.base_value import LazyValueWrapper
|
||||||
@@ -29,7 +31,7 @@ class ExactValue(LazyValueWrapper):
|
|||||||
if name in ('get_safe_value', 'execute_operation', 'access_handle',
|
if name in ('get_safe_value', 'execute_operation', 'access_handle',
|
||||||
'negate', 'py__bool__', 'is_compiled'):
|
'negate', 'py__bool__', 'is_compiled'):
|
||||||
return getattr(self._compiled_value, name)
|
return getattr(self._compiled_value, name)
|
||||||
return super(ExactValue, self).__getattribute__(name)
|
return super().__getattribute__(name)
|
||||||
|
|
||||||
def _get_wrapped_value(self):
|
def _get_wrapped_value(self):
|
||||||
instance, = builtin_from_name(
|
instance, = builtin_from_name(
|
||||||
@@ -45,7 +47,7 @@ def create_simple_object(inference_state, obj):
|
|||||||
Only allows creations of objects that are easily picklable across Python
|
Only allows creations of objects that are easily picklable across Python
|
||||||
versions.
|
versions.
|
||||||
"""
|
"""
|
||||||
assert type(obj) in (int, float, str, bytes, unicode, slice, complex, bool), obj
|
assert type(obj) in (int, float, str, bytes, slice, complex, bool), repr(obj)
|
||||||
compiled_value = create_from_access_path(
|
compiled_value = create_from_access_path(
|
||||||
inference_state,
|
inference_state,
|
||||||
inference_state.compiled_subprocess.create_simple_object(obj)
|
inference_state.compiled_subprocess.create_simple_object(obj)
|
||||||
@@ -54,7 +56,7 @@ def create_simple_object(inference_state, obj):
|
|||||||
|
|
||||||
|
|
||||||
def get_string_value_set(inference_state):
|
def get_string_value_set(inference_state):
|
||||||
return builtin_from_name(inference_state, u'str').execute_with_values()
|
return builtin_from_name(inference_state, 'str').execute_with_values()
|
||||||
|
|
||||||
|
|
||||||
def load_module(inference_state, dotted_name, **kwargs):
|
def load_module(inference_state, dotted_name, **kwargs):
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from __future__ import print_function
|
|
||||||
import inspect
|
import inspect
|
||||||
import types
|
import types
|
||||||
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
import operator as op
|
import operator as op
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import warnings
|
import warnings
|
||||||
import re
|
import re
|
||||||
|
import builtins
|
||||||
|
import typing
|
||||||
|
|
||||||
from jedi._compatibility import unicode, is_py3, builtins, \
|
|
||||||
py_version, force_unicode
|
|
||||||
from jedi.inference.compiled.getattr_static import getattr_static
|
from jedi.inference.compiled.getattr_static import getattr_static
|
||||||
|
|
||||||
ALLOWED_GETITEM_TYPES = (str, list, tuple, unicode, bytes, bytearray, dict)
|
ALLOWED_GETITEM_TYPES = (str, list, tuple, bytes, bytearray, dict)
|
||||||
|
|
||||||
MethodDescriptorType = type(str.replace)
|
MethodDescriptorType = type(str.replace)
|
||||||
# These are not considered classes and access is granted even though they have
|
# These are not considered classes and access is granted even though they have
|
||||||
@@ -28,17 +28,12 @@ NOT_CLASS_TYPES = (
|
|||||||
types.MethodType,
|
types.MethodType,
|
||||||
types.ModuleType,
|
types.ModuleType,
|
||||||
types.TracebackType,
|
types.TracebackType,
|
||||||
MethodDescriptorType
|
MethodDescriptorType,
|
||||||
|
types.MappingProxyType,
|
||||||
|
types.SimpleNamespace,
|
||||||
|
types.DynamicClassAttribute,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_py3:
|
|
||||||
NOT_CLASS_TYPES += (
|
|
||||||
types.MappingProxyType,
|
|
||||||
types.SimpleNamespace,
|
|
||||||
types.DynamicClassAttribute,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Those types don't exist in typing.
|
# Those types don't exist in typing.
|
||||||
MethodDescriptorType = type(str.replace)
|
MethodDescriptorType = type(str.replace)
|
||||||
WrapperDescriptorType = type(set.__iter__)
|
WrapperDescriptorType = type(set.__iter__)
|
||||||
@@ -123,13 +118,18 @@ def load_module(inference_state, dotted_name, sys_path):
|
|||||||
__import__(dotted_name)
|
__import__(dotted_name)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# If a module is "corrupt" or not really a Python module or whatever.
|
# If a module is "corrupt" or not really a Python module or whatever.
|
||||||
print('Module %s not importable in path %s.' % (dotted_name, sys_path), file=sys.stderr)
|
warnings.warn(
|
||||||
|
"Module %s not importable in path %s." % (dotted_name, sys_path),
|
||||||
|
UserWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
# Since __import__ pretty much makes code execution possible, just
|
# Since __import__ pretty much makes code execution possible, just
|
||||||
# catch any error here and print it.
|
# catch any error here and print it.
|
||||||
import traceback
|
warnings.warn(
|
||||||
print("Cannot import:\n%s" % traceback.format_exc(), file=sys.stderr)
|
"Cannot import:\n%s" % traceback.format_exc(), UserWarning, stacklevel=2
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
sys.path = temp
|
sys.path = temp
|
||||||
@@ -140,42 +140,29 @@ def load_module(inference_state, dotted_name, sys_path):
|
|||||||
return create_access_path(inference_state, module)
|
return create_access_path(inference_state, module)
|
||||||
|
|
||||||
|
|
||||||
class AccessPath(object):
|
class AccessPath:
|
||||||
def __init__(self, accesses):
|
def __init__(self, accesses):
|
||||||
self.accesses = accesses
|
self.accesses = accesses
|
||||||
|
|
||||||
# Writing both of these methods here looks a bit ridiculous. However with
|
|
||||||
# the differences of Python 2/3 it's actually necessary, because we will
|
|
||||||
# otherwise have a accesses attribute that is bytes instead of unicode.
|
|
||||||
def __getstate__(self):
|
|
||||||
return self.accesses
|
|
||||||
|
|
||||||
def __setstate__(self, value):
|
|
||||||
self.accesses = value
|
|
||||||
|
|
||||||
|
|
||||||
def create_access_path(inference_state, obj):
|
def create_access_path(inference_state, obj):
|
||||||
access = create_access(inference_state, obj)
|
access = create_access(inference_state, obj)
|
||||||
return AccessPath(access.get_access_path_tuples())
|
return AccessPath(access.get_access_path_tuples())
|
||||||
|
|
||||||
|
|
||||||
def _force_unicode_decorator(func):
|
|
||||||
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
|
|
||||||
|
|
||||||
|
|
||||||
def get_api_type(obj):
|
def get_api_type(obj):
|
||||||
if inspect.isclass(obj):
|
if inspect.isclass(obj):
|
||||||
return u'class'
|
return 'class'
|
||||||
elif inspect.ismodule(obj):
|
elif inspect.ismodule(obj):
|
||||||
return u'module'
|
return 'module'
|
||||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||||
return u'function'
|
return 'function'
|
||||||
# Everything else...
|
# Everything else...
|
||||||
return u'instance'
|
return 'instance'
|
||||||
|
|
||||||
|
|
||||||
class DirectObjectAccess(object):
|
class DirectObjectAccess:
|
||||||
def __init__(self, inference_state, obj):
|
def __init__(self, inference_state, obj):
|
||||||
self._inference_state = inference_state
|
self._inference_state = inference_state
|
||||||
self._obj = obj
|
self._obj = obj
|
||||||
@@ -199,7 +186,7 @@ class DirectObjectAccess(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
return force_unicode(inspect.getdoc(self._obj)) or u''
|
return inspect.getdoc(self._obj) or ''
|
||||||
|
|
||||||
def py__name__(self):
|
def py__name__(self):
|
||||||
if not _is_class_instance(self._obj) or \
|
if not _is_class_instance(self._obj) or \
|
||||||
@@ -214,7 +201,7 @@ class DirectObjectAccess(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return force_unicode(cls.__name__)
|
return cls.__name__
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -260,26 +247,23 @@ class DirectObjectAccess(object):
|
|||||||
# Avoid some weird hacks that would just fail, because they cannot be
|
# Avoid some weird hacks that would just fail, because they cannot be
|
||||||
# used by pickle.
|
# used by pickle.
|
||||||
if not isinstance(paths, list) \
|
if not isinstance(paths, list) \
|
||||||
or not all(isinstance(p, (bytes, unicode)) for p in paths):
|
or not all(isinstance(p, str) for p in paths):
|
||||||
return None
|
return None
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
@_force_unicode_decorator
|
|
||||||
@shorten_repr
|
@shorten_repr
|
||||||
def get_repr(self):
|
def get_repr(self):
|
||||||
builtins = 'builtins', '__builtin__'
|
|
||||||
|
|
||||||
if inspect.ismodule(self._obj):
|
if inspect.ismodule(self._obj):
|
||||||
return repr(self._obj)
|
return repr(self._obj)
|
||||||
# Try to avoid execution of the property.
|
# Try to avoid execution of the property.
|
||||||
if safe_getattr(self._obj, '__module__', default='') in builtins:
|
if safe_getattr(self._obj, '__module__', default='') == 'builtins':
|
||||||
return repr(self._obj)
|
return repr(self._obj)
|
||||||
|
|
||||||
type_ = type(self._obj)
|
type_ = type(self._obj)
|
||||||
if type_ == type:
|
if type_ == type:
|
||||||
return type.__repr__(self._obj)
|
return type.__repr__(self._obj)
|
||||||
|
|
||||||
if safe_getattr(type_, '__module__', default='') in builtins:
|
if safe_getattr(type_, '__module__', default='') == 'builtins':
|
||||||
# Allow direct execution of repr for builtins.
|
# Allow direct execution of repr for builtins.
|
||||||
return repr(self._obj)
|
return repr(self._obj)
|
||||||
return object.__repr__(self._obj)
|
return object.__repr__(self._obj)
|
||||||
@@ -310,10 +294,10 @@ class DirectObjectAccess(object):
|
|||||||
name = try_to_get_name(type(self._obj))
|
name = try_to_get_name(type(self._obj))
|
||||||
if name is None:
|
if name is None:
|
||||||
return ()
|
return ()
|
||||||
return tuple(force_unicode(n) for n in name.split('.'))
|
return tuple(name.split('.'))
|
||||||
|
|
||||||
def dir(self):
|
def dir(self):
|
||||||
return list(map(force_unicode, dir(self._obj)))
|
return dir(self._obj)
|
||||||
|
|
||||||
def has_iter(self):
|
def has_iter(self):
|
||||||
try:
|
try:
|
||||||
@@ -396,7 +380,7 @@ class DirectObjectAccess(object):
|
|||||||
return [self._create_access(module), access]
|
return [self._create_access(module), access]
|
||||||
|
|
||||||
def get_safe_value(self):
|
def get_safe_value(self):
|
||||||
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice) or self._obj is None:
|
if type(self._obj) in (bool, bytes, float, int, str, slice) or self._obj is None:
|
||||||
return self._obj
|
return self._obj
|
||||||
raise ValueError("Object is type %s and not simple" % type(self._obj))
|
raise ValueError("Object is type %s and not simple" % type(self._obj))
|
||||||
|
|
||||||
@@ -464,9 +448,6 @@ class DirectObjectAccess(object):
|
|||||||
"""
|
"""
|
||||||
Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
|
Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
|
||||||
"""
|
"""
|
||||||
if sys.version_info < (3, 5):
|
|
||||||
return None, ()
|
|
||||||
|
|
||||||
name = None
|
name = None
|
||||||
args = ()
|
args = ()
|
||||||
if safe_getattr(self._obj, '__module__', default='') == 'typing':
|
if safe_getattr(self._obj, '__module__', default='') == 'typing':
|
||||||
@@ -484,6 +465,9 @@ class DirectObjectAccess(object):
|
|||||||
def needs_type_completions(self):
|
def needs_type_completions(self):
|
||||||
return inspect.isclass(self._obj) and self._obj != type
|
return inspect.isclass(self._obj) and self._obj != type
|
||||||
|
|
||||||
|
def _annotation_to_str(self, annotation):
|
||||||
|
return inspect.formatannotation(annotation)
|
||||||
|
|
||||||
def get_signature_params(self):
|
def get_signature_params(self):
|
||||||
return [
|
return [
|
||||||
SignatureParam(
|
SignatureParam(
|
||||||
@@ -493,31 +477,13 @@ class DirectObjectAccess(object):
|
|||||||
default_string=repr(p.default),
|
default_string=repr(p.default),
|
||||||
has_annotation=p.annotation is not p.empty,
|
has_annotation=p.annotation is not p.empty,
|
||||||
annotation=self._create_access_path(p.annotation),
|
annotation=self._create_access_path(p.annotation),
|
||||||
annotation_string=str(p.annotation),
|
annotation_string=self._annotation_to_str(p.annotation),
|
||||||
kind_name=str(p.kind)
|
kind_name=str(p.kind)
|
||||||
) for p in self._get_signature().parameters.values()
|
) for p in self._get_signature().parameters.values()
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_signature(self):
|
def _get_signature(self):
|
||||||
obj = self._obj
|
obj = self._obj
|
||||||
if py_version < 33:
|
|
||||||
raise ValueError("inspect.signature was introduced in 3.3")
|
|
||||||
if py_version == 34:
|
|
||||||
# In 3.4 inspect.signature are wrong for str and int. This has
|
|
||||||
# been fixed in 3.5. The signature of object is returned,
|
|
||||||
# because no signature was found for str. Here we imitate 3.5
|
|
||||||
# logic and just ignore the signature if the magic methods
|
|
||||||
# don't match object.
|
|
||||||
# 3.3 doesn't even have the logic and returns nothing for str
|
|
||||||
# and classes that inherit from object.
|
|
||||||
user_def = inspect._signature_get_user_defined_method
|
|
||||||
if (inspect.isclass(obj)
|
|
||||||
and not user_def(type(obj), '__init__')
|
|
||||||
and not user_def(type(obj), '__new__')
|
|
||||||
and (obj.__init__ != object.__init__
|
|
||||||
or obj.__new__ != object.__new__)):
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return inspect.signature(obj)
|
return inspect.signature(obj)
|
||||||
except (RuntimeError, TypeError):
|
except (RuntimeError, TypeError):
|
||||||
@@ -536,15 +502,9 @@ class DirectObjectAccess(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 2 doesn't have typing.
|
o = typing.get_type_hints(self._obj).get('return')
|
||||||
import typing
|
except Exception:
|
||||||
except ImportError:
|
|
||||||
pass
|
pass
|
||||||
else:
|
|
||||||
try:
|
|
||||||
o = typing.get_type_hints(self._obj).get('return')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return self._create_access_path(o)
|
return self._create_access_path(o)
|
||||||
|
|
||||||
@@ -557,7 +517,7 @@ class DirectObjectAccess(object):
|
|||||||
objects of an objects
|
objects of an objects
|
||||||
"""
|
"""
|
||||||
tuples = dict(
|
tuples = dict(
|
||||||
(force_unicode(name), self.is_allowed_getattr(name))
|
(name, self.is_allowed_getattr(name))
|
||||||
for name in self.dir()
|
for name in self.dir()
|
||||||
)
|
)
|
||||||
return self.needs_type_completions(), tuples
|
return self.needs_type_completions(), tuples
|
||||||
@@ -570,4 +530,6 @@ def _is_class_instance(obj):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)
|
# The isinstance check for cls is just there so issubclass doesn't
|
||||||
|
# raise an exception.
|
||||||
|
return cls != type and isinstance(cls, type) and not issubclass(cls, NOT_CLASS_TYPES)
|
||||||
@@ -6,7 +6,7 @@ information returned to enable Jedi to make decisions.
|
|||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from jedi._compatibility import py_version
|
from jedi import debug
|
||||||
|
|
||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ def _is_type(obj):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _shadowed_dict_newstyle(klass):
|
def _shadowed_dict(klass):
|
||||||
dict_attr = type.__dict__["__dict__"]
|
dict_attr = type.__dict__["__dict__"]
|
||||||
for entry in _static_getmro(klass):
|
for entry in _static_getmro(klass):
|
||||||
try:
|
try:
|
||||||
@@ -53,74 +53,19 @@ def _shadowed_dict_newstyle(klass):
|
|||||||
return _sentinel
|
return _sentinel
|
||||||
|
|
||||||
|
|
||||||
def _static_getmro_newstyle(klass):
|
def _static_getmro(klass):
|
||||||
return type.__dict__['__mro__'].__get__(klass)
|
mro = type.__dict__['__mro__'].__get__(klass)
|
||||||
|
if not isinstance(mro, (tuple, list)):
|
||||||
|
# There are unfortunately no tests for this, I was not able to
|
||||||
if py_version >= 30:
|
# reproduce this in pure Python. However should still solve the issue
|
||||||
_shadowed_dict = _shadowed_dict_newstyle
|
# raised in GH #1517.
|
||||||
_get_type = type
|
debug.warning('mro of %s returned %s, should be a tuple' % (klass, mro))
|
||||||
_static_getmro = _static_getmro_newstyle
|
return ()
|
||||||
else:
|
return mro
|
||||||
def _shadowed_dict(klass):
|
|
||||||
"""
|
|
||||||
In Python 2 __dict__ is not overwritable:
|
|
||||||
|
|
||||||
class Foo(object): pass
|
|
||||||
setattr(Foo, '__dict__', 4)
|
|
||||||
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in <module>
|
|
||||||
TypeError: __dict__ must be a dictionary object
|
|
||||||
|
|
||||||
It applies to both newstyle and oldstyle classes:
|
|
||||||
|
|
||||||
class Foo(object): pass
|
|
||||||
setattr(Foo, '__dict__', 4)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in <module>
|
|
||||||
AttributeError: attribute '__dict__' of 'type' objects is not writable
|
|
||||||
|
|
||||||
It also applies to instances of those objects. However to keep things
|
|
||||||
straight forward, newstyle classes always use the complicated way of
|
|
||||||
accessing it while oldstyle classes just use getattr.
|
|
||||||
"""
|
|
||||||
if type(klass) is _oldstyle_class_type:
|
|
||||||
return getattr(klass, '__dict__', _sentinel)
|
|
||||||
return _shadowed_dict_newstyle(klass)
|
|
||||||
|
|
||||||
class _OldStyleClass:
|
|
||||||
pass
|
|
||||||
|
|
||||||
_oldstyle_instance_type = type(_OldStyleClass())
|
|
||||||
_oldstyle_class_type = type(_OldStyleClass)
|
|
||||||
|
|
||||||
def _get_type(obj):
|
|
||||||
type_ = object.__getattribute__(obj, '__class__')
|
|
||||||
if type_ is _oldstyle_instance_type:
|
|
||||||
# Somehow for old style classes we need to access it directly.
|
|
||||||
return obj.__class__
|
|
||||||
return type_
|
|
||||||
|
|
||||||
def _static_getmro(klass):
|
|
||||||
if type(klass) is _oldstyle_class_type:
|
|
||||||
def oldstyle_mro(klass):
|
|
||||||
"""
|
|
||||||
Oldstyle mro is a really simplistic way of look up mro:
|
|
||||||
https://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python
|
|
||||||
"""
|
|
||||||
yield klass
|
|
||||||
for base in klass.__bases__:
|
|
||||||
for yield_from in oldstyle_mro(base):
|
|
||||||
yield yield_from
|
|
||||||
|
|
||||||
return oldstyle_mro(klass)
|
|
||||||
|
|
||||||
return _static_getmro_newstyle(klass)
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_hasattr(obj, name):
|
def _safe_hasattr(obj, name):
|
||||||
return _check_class(_get_type(obj), name) is not _sentinel
|
return _check_class(type(obj), name) is not _sentinel
|
||||||
|
|
||||||
|
|
||||||
def _safe_is_data_descriptor(obj):
|
def _safe_is_data_descriptor(obj):
|
||||||
@@ -143,7 +88,7 @@ def getattr_static(obj, attr, default=_sentinel):
|
|||||||
"""
|
"""
|
||||||
instance_result = _sentinel
|
instance_result = _sentinel
|
||||||
if not _is_type(obj):
|
if not _is_type(obj):
|
||||||
klass = _get_type(obj)
|
klass = type(obj)
|
||||||
dict_attr = _shadowed_dict(klass)
|
dict_attr = _shadowed_dict(klass)
|
||||||
if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType):
|
if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType):
|
||||||
instance_result = _check_instance(obj, attr)
|
instance_result = _check_instance(obj, attr)
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ Used only for REPL Completion.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
from pathlib import Path
|
||||||
import sys
|
|
||||||
|
|
||||||
from jedi.parser_utils import get_cached_code_lines
|
from jedi.parser_utils import get_cached_code_lines
|
||||||
|
|
||||||
from jedi._compatibility import unwrap
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.inference import compiled
|
from jedi.inference import compiled
|
||||||
@@ -44,7 +42,7 @@ class MixedObject(ValueWrapper):
|
|||||||
to modify the runtime.
|
to modify the runtime.
|
||||||
"""
|
"""
|
||||||
def __init__(self, compiled_value, tree_value):
|
def __init__(self, compiled_value, tree_value):
|
||||||
super(MixedObject, self).__init__(tree_value)
|
super().__init__(tree_value)
|
||||||
self.compiled_value = compiled_value
|
self.compiled_value = compiled_value
|
||||||
self.access_handle = compiled_value.access_handle
|
self.access_handle = compiled_value.access_handle
|
||||||
|
|
||||||
@@ -71,12 +69,22 @@ class MixedObject(ValueWrapper):
|
|||||||
else:
|
else:
|
||||||
return self.compiled_value.get_safe_value(default)
|
return self.compiled_value.get_safe_value(default)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def array_type(self):
|
||||||
|
return self.compiled_value.array_type
|
||||||
|
|
||||||
|
def get_key_values(self):
|
||||||
|
return self.compiled_value.get_key_values()
|
||||||
|
|
||||||
def py__simple_getitem__(self, index):
|
def py__simple_getitem__(self, index):
|
||||||
python_object = self.compiled_value.access_handle.access._obj
|
python_object = self.compiled_value.access_handle.access._obj
|
||||||
if type(python_object) in ALLOWED_GETITEM_TYPES:
|
if type(python_object) in ALLOWED_GETITEM_TYPES:
|
||||||
return self.compiled_value.py__simple_getitem__(index)
|
return self.compiled_value.py__simple_getitem__(index)
|
||||||
return self._wrapped_value.py__simple_getitem__(index)
|
return self._wrapped_value.py__simple_getitem__(index)
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return self.compiled_value.negate()
|
||||||
|
|
||||||
def _as_context(self):
|
def _as_context(self):
|
||||||
if self.parent_context is None:
|
if self.parent_context is None:
|
||||||
return MixedModuleContext(self)
|
return MixedModuleContext(self)
|
||||||
@@ -105,7 +113,7 @@ class MixedName(NameWrapper):
|
|||||||
The ``CompiledName._compiled_value`` is our MixedObject.
|
The ``CompiledName._compiled_value`` is our MixedObject.
|
||||||
"""
|
"""
|
||||||
def __init__(self, wrapped_name, parent_tree_value):
|
def __init__(self, wrapped_name, parent_tree_value):
|
||||||
super(MixedName, self).__init__(wrapped_name)
|
super().__init__(wrapped_name)
|
||||||
self._parent_tree_value = parent_tree_value
|
self._parent_tree_value = parent_tree_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -131,12 +139,12 @@ class MixedName(NameWrapper):
|
|||||||
|
|
||||||
class MixedObjectFilter(compiled.CompiledValueFilter):
|
class MixedObjectFilter(compiled.CompiledValueFilter):
|
||||||
def __init__(self, inference_state, compiled_value, tree_value):
|
def __init__(self, inference_state, compiled_value, tree_value):
|
||||||
super(MixedObjectFilter, self).__init__(inference_state, compiled_value)
|
super().__init__(inference_state, compiled_value)
|
||||||
self._tree_value = tree_value
|
self._tree_value = tree_value
|
||||||
|
|
||||||
def _create_name(self, name):
|
def _create_name(self, name):
|
||||||
return MixedName(
|
return MixedName(
|
||||||
super(MixedObjectFilter, self)._create_name(name),
|
super()._create_name(name),
|
||||||
self._tree_value,
|
self._tree_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -153,12 +161,11 @@ def _load_module(inference_state, path):
|
|||||||
|
|
||||||
def _get_object_to_check(python_object):
|
def _get_object_to_check(python_object):
|
||||||
"""Check if inspect.getfile has a chance to find the source."""
|
"""Check if inspect.getfile has a chance to find the source."""
|
||||||
if sys.version_info[0] > 2:
|
try:
|
||||||
try:
|
python_object = inspect.unwrap(python_object)
|
||||||
python_object = unwrap(python_object)
|
except ValueError:
|
||||||
except ValueError:
|
# Can return a ValueError when it wraps around
|
||||||
# Can return a ValueError when it wraps around
|
pass
|
||||||
pass
|
|
||||||
|
|
||||||
if (inspect.ismodule(python_object)
|
if (inspect.ismodule(python_object)
|
||||||
or inspect.isclass(python_object)
|
or inspect.isclass(python_object)
|
||||||
@@ -183,8 +190,16 @@ def _find_syntax_node_name(inference_state, python_object):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||||
return None
|
return None
|
||||||
if path is None or not os.path.exists(path):
|
path = None if path is None else Path(path)
|
||||||
# The path might not exist or be e.g. <stdin>.
|
try:
|
||||||
|
if path is None or not path.exists():
|
||||||
|
# The path might not exist or be e.g. <stdin>.
|
||||||
|
return None
|
||||||
|
except OSError:
|
||||||
|
# Might raise an OSError on Windows:
|
||||||
|
#
|
||||||
|
# [WinError 123] The filename, directory name, or volume label
|
||||||
|
# syntax is incorrect: '<string>'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
file_io = FileIO(path)
|
file_io = FileIO(path)
|
||||||
|
|||||||
@@ -9,19 +9,14 @@ goals:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import queue
|
||||||
import subprocess
|
import subprocess
|
||||||
import socket
|
|
||||||
import errno
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import weakref
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
try:
|
|
||||||
from queue import Queue, Empty
|
|
||||||
except ImportError:
|
|
||||||
from Queue import Queue, Empty # python 2.7
|
|
||||||
|
|
||||||
from jedi._compatibility import queue, is_py3, force_unicode, \
|
from jedi._compatibility import pickle_dump, pickle_load
|
||||||
pickle_dump, pickle_load, GeneralizedPopen, weakref
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.inference.compiled.subprocess import functions
|
from jedi.inference.compiled.subprocess import functions
|
||||||
@@ -31,11 +26,27 @@ from jedi.api.exceptions import InternalError
|
|||||||
|
|
||||||
|
|
||||||
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
|
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
|
||||||
|
PICKLE_PROTOCOL = 4
|
||||||
|
|
||||||
|
|
||||||
def _enqueue_output(out, queue):
|
def _GeneralizedPopen(*args, **kwargs):
|
||||||
|
if os.name == 'nt':
|
||||||
|
try:
|
||||||
|
# Was introduced in Python 3.7.
|
||||||
|
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
|
||||||
|
except AttributeError:
|
||||||
|
CREATE_NO_WINDOW = 0x08000000
|
||||||
|
kwargs['creationflags'] = CREATE_NO_WINDOW
|
||||||
|
# The child process doesn't need file descriptors except 0, 1, 2.
|
||||||
|
# This is unix only.
|
||||||
|
kwargs['close_fds'] = 'posix' in sys.builtin_module_names
|
||||||
|
|
||||||
|
return subprocess.Popen(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _enqueue_output(out, queue_):
|
||||||
for line in iter(out.readline, b''):
|
for line in iter(out.readline, b''):
|
||||||
queue.put(line)
|
queue_.put(line)
|
||||||
|
|
||||||
|
|
||||||
def _add_stderr_to_debug(stderr_queue):
|
def _add_stderr_to_debug(stderr_queue):
|
||||||
@@ -46,7 +57,7 @@ def _add_stderr_to_debug(stderr_queue):
|
|||||||
line = stderr_queue.get_nowait()
|
line = stderr_queue.get_nowait()
|
||||||
line = line.decode('utf-8', 'replace')
|
line = line.decode('utf-8', 'replace')
|
||||||
debug.warning('stderr output: %s' % line.rstrip('\n'))
|
debug.warning('stderr output: %s' % line.rstrip('\n'))
|
||||||
except Empty:
|
except queue.Empty:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +81,7 @@ def _cleanup_process(process, thread):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _InferenceStateProcess(object):
|
class _InferenceStateProcess:
|
||||||
def __init__(self, inference_state):
|
def __init__(self, inference_state):
|
||||||
self._inference_state_weakref = weakref.ref(inference_state)
|
self._inference_state_weakref = weakref.ref(inference_state)
|
||||||
self._inference_state_id = id(inference_state)
|
self._inference_state_id = id(inference_state)
|
||||||
@@ -105,7 +116,7 @@ class InferenceStateSameProcess(_InferenceStateProcess):
|
|||||||
|
|
||||||
class InferenceStateSubprocess(_InferenceStateProcess):
|
class InferenceStateSubprocess(_InferenceStateProcess):
|
||||||
def __init__(self, inference_state, compiled_subprocess):
|
def __init__(self, inference_state, compiled_subprocess):
|
||||||
super(InferenceStateSubprocess, self).__init__(inference_state)
|
super().__init__(inference_state)
|
||||||
self._used = False
|
self._used = False
|
||||||
self._compiled_subprocess = compiled_subprocess
|
self._compiled_subprocess = compiled_subprocess
|
||||||
|
|
||||||
@@ -151,22 +162,20 @@ class InferenceStateSubprocess(_InferenceStateProcess):
|
|||||||
self._compiled_subprocess.delete_inference_state(self._inference_state_id)
|
self._compiled_subprocess.delete_inference_state(self._inference_state_id)
|
||||||
|
|
||||||
|
|
||||||
class CompiledSubprocess(object):
|
class CompiledSubprocess:
|
||||||
is_crashed = False
|
is_crashed = False
|
||||||
# Start with 2, gets set after _get_info.
|
|
||||||
_pickle_protocol = 2
|
|
||||||
|
|
||||||
def __init__(self, executable):
|
def __init__(self, executable, env_vars=None):
|
||||||
self._executable = executable
|
self._executable = executable
|
||||||
|
self._env_vars = env_vars
|
||||||
self._inference_state_deletion_queue = queue.deque()
|
self._inference_state_deletion_queue = queue.deque()
|
||||||
self._cleanup_callable = lambda: None
|
self._cleanup_callable = lambda: None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % (
|
return '<%s _executable=%r, is_crashed=%r, pid=%r>' % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
self._executable,
|
self._executable,
|
||||||
self._pickle_protocol,
|
|
||||||
self.is_crashed,
|
self.is_crashed,
|
||||||
pid,
|
pid,
|
||||||
)
|
)
|
||||||
@@ -181,16 +190,14 @@ class CompiledSubprocess(object):
|
|||||||
os.path.dirname(os.path.dirname(parso_path)),
|
os.path.dirname(os.path.dirname(parso_path)),
|
||||||
'.'.join(str(x) for x in sys.version_info[:3]),
|
'.'.join(str(x) for x in sys.version_info[:3]),
|
||||||
)
|
)
|
||||||
process = GeneralizedPopen(
|
process = _GeneralizedPopen(
|
||||||
args,
|
args,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
# Use system default buffering on Python 2 to improve performance
|
env=self._env_vars
|
||||||
# (this is already the case on Python 3).
|
|
||||||
bufsize=-1
|
|
||||||
)
|
)
|
||||||
self._stderr_queue = Queue()
|
self._stderr_queue = queue.Queue()
|
||||||
self._stderr_thread = t = Thread(
|
self._stderr_thread = t = Thread(
|
||||||
target=_enqueue_output,
|
target=_enqueue_output,
|
||||||
args=(process.stderr, self._stderr_queue)
|
args=(process.stderr, self._stderr_queue)
|
||||||
@@ -229,20 +236,10 @@ class CompiledSubprocess(object):
|
|||||||
if self.is_crashed:
|
if self.is_crashed:
|
||||||
raise InternalError("The subprocess %s has crashed." % self._executable)
|
raise InternalError("The subprocess %s has crashed." % self._executable)
|
||||||
|
|
||||||
if not is_py3:
|
|
||||||
# Python 2 compatibility
|
|
||||||
kwargs = {force_unicode(key): value for key, value in kwargs.items()}
|
|
||||||
|
|
||||||
data = inference_state_id, function, args, kwargs
|
data = inference_state_id, function, args, kwargs
|
||||||
try:
|
try:
|
||||||
pickle_dump(data, self._get_process().stdin, self._pickle_protocol)
|
pickle_dump(data, self._get_process().stdin, PICKLE_PROTOCOL)
|
||||||
except (socket.error, IOError) as e:
|
except BrokenPipeError:
|
||||||
# Once Python2 will be removed we can just use `BrokenPipeError`.
|
|
||||||
# Also, somehow in windows it returns EINVAL instead of EPIPE if
|
|
||||||
# the subprocess dies.
|
|
||||||
if e.errno not in (errno.EPIPE, errno.EINVAL):
|
|
||||||
# Not a broken pipe
|
|
||||||
raise
|
|
||||||
self._kill()
|
self._kill()
|
||||||
raise InternalError("The subprocess %s was killed. Maybe out of memory?"
|
raise InternalError("The subprocess %s was killed. Maybe out of memory?"
|
||||||
% self._executable)
|
% self._executable)
|
||||||
@@ -283,13 +280,12 @@ class CompiledSubprocess(object):
|
|||||||
self._inference_state_deletion_queue.append(inference_state_id)
|
self._inference_state_deletion_queue.append(inference_state_id)
|
||||||
|
|
||||||
|
|
||||||
class Listener(object):
|
class Listener:
|
||||||
def __init__(self, pickle_protocol):
|
def __init__(self):
|
||||||
self._inference_states = {}
|
self._inference_states = {}
|
||||||
# TODO refactor so we don't need to process anymore just handle
|
# TODO refactor so we don't need to process anymore just handle
|
||||||
# controlling.
|
# controlling.
|
||||||
self._process = _InferenceStateProcess(Listener)
|
self._process = _InferenceStateProcess(Listener)
|
||||||
self._pickle_protocol = pickle_protocol
|
|
||||||
|
|
||||||
def _get_inference_state(self, function, inference_state_id):
|
def _get_inference_state(self, function, inference_state_id):
|
||||||
from jedi.inference import InferenceState
|
from jedi.inference import InferenceState
|
||||||
@@ -297,7 +293,7 @@ class Listener(object):
|
|||||||
try:
|
try:
|
||||||
inference_state = self._inference_states[inference_state_id]
|
inference_state = self._inference_states[inference_state_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
from jedi.api.environment import InterpreterEnvironment
|
from jedi import InterpreterEnvironment
|
||||||
inference_state = InferenceState(
|
inference_state = InferenceState(
|
||||||
# The project is not actually needed. Nothing should need to
|
# The project is not actually needed. Nothing should need to
|
||||||
# access it.
|
# access it.
|
||||||
@@ -332,15 +328,8 @@ class Listener(object):
|
|||||||
# because stdout is used for IPC.
|
# because stdout is used for IPC.
|
||||||
sys.stdout = open(os.devnull, 'w')
|
sys.stdout = open(os.devnull, 'w')
|
||||||
stdin = sys.stdin
|
stdin = sys.stdin
|
||||||
if sys.version_info[0] > 2:
|
stdout = stdout.buffer
|
||||||
stdout = stdout.buffer
|
stdin = stdin.buffer
|
||||||
stdin = stdin.buffer
|
|
||||||
# Python 2 opens streams in text mode on Windows. Set stdout and stdin
|
|
||||||
# to binary mode.
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
import msvcrt
|
|
||||||
msvcrt.setmode(stdout.fileno(), os.O_BINARY)
|
|
||||||
msvcrt.setmode(stdin.fileno(), os.O_BINARY)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -354,10 +343,10 @@ class Listener(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = True, traceback.format_exc(), e
|
result = True, traceback.format_exc(), e
|
||||||
|
|
||||||
pickle_dump(result, stdout, self._pickle_protocol)
|
pickle_dump(result, stdout, PICKLE_PROTOCOL)
|
||||||
|
|
||||||
|
|
||||||
class AccessHandle(object):
|
class AccessHandle:
|
||||||
def __init__(self, subprocess, access, id_):
|
def __init__(self, subprocess, access, id_):
|
||||||
self.access = access
|
self.access = access
|
||||||
self._subprocess = subprocess
|
self._subprocess = subprocess
|
||||||
@@ -383,9 +372,8 @@ class AccessHandle(object):
|
|||||||
if name in ('id', 'access') or name.startswith('_'):
|
if name in ('id', 'access') or name.startswith('_'):
|
||||||
raise AttributeError("Something went wrong with unpickling")
|
raise AttributeError("Something went wrong with unpickling")
|
||||||
|
|
||||||
# if not is_py3: print >> sys.stderr, name
|
|
||||||
# print('getattr', name, file=sys.stderr)
|
# print('getattr', name, file=sys.stderr)
|
||||||
return partial(self._workaround, force_unicode(name))
|
return partial(self._workaround, name)
|
||||||
|
|
||||||
def _workaround(self, name, *args, **kwargs):
|
def _workaround(self, name, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from importlib.abc import MetaPathFinder
|
||||||
|
from importlib.machinery import PathFinder
|
||||||
|
|
||||||
|
# Remove the first entry, because it's simply a directory entry that equals
|
||||||
|
# this directory.
|
||||||
|
del sys.path[0]
|
||||||
|
|
||||||
|
|
||||||
def _get_paths():
|
def _get_paths():
|
||||||
@@ -11,45 +17,24 @@ def _get_paths():
|
|||||||
return {'jedi': _jedi_path, 'parso': _parso_path}
|
return {'jedi': _jedi_path, 'parso': _parso_path}
|
||||||
|
|
||||||
|
|
||||||
# Remove the first entry, because it's simply a directory entry that equals
|
class _ExactImporter(MetaPathFinder):
|
||||||
# this directory.
|
def __init__(self, path_dct):
|
||||||
del sys.path[0]
|
self._path_dct = path_dct
|
||||||
|
|
||||||
if sys.version_info > (3, 4):
|
def find_module(self, fullname, path=None):
|
||||||
from importlib.machinery import PathFinder
|
if path is None and fullname in self._path_dct:
|
||||||
|
p = self._path_dct[fullname]
|
||||||
|
loader = PathFinder.find_module(fullname, path=[p])
|
||||||
|
return loader
|
||||||
|
return None
|
||||||
|
|
||||||
class _ExactImporter(object):
|
|
||||||
def __init__(self, path_dct):
|
|
||||||
self._path_dct = path_dct
|
|
||||||
|
|
||||||
def find_module(self, fullname, path=None):
|
|
||||||
if path is None and fullname in self._path_dct:
|
|
||||||
p = self._path_dct[fullname]
|
|
||||||
loader = PathFinder.find_module(fullname, path=[p])
|
|
||||||
return loader
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Try to import jedi/parso.
|
|
||||||
sys.meta_path.insert(0, _ExactImporter(_get_paths()))
|
|
||||||
from jedi.inference.compiled import subprocess # NOQA
|
|
||||||
sys.meta_path.pop(0)
|
|
||||||
else:
|
|
||||||
import imp
|
|
||||||
|
|
||||||
def load(name):
|
|
||||||
paths = list(_get_paths().values())
|
|
||||||
fp, pathname, description = imp.find_module(name, paths)
|
|
||||||
return imp.load_module(name, fp, pathname, description)
|
|
||||||
|
|
||||||
load('parso')
|
|
||||||
load('jedi')
|
|
||||||
from jedi.inference.compiled import subprocess # NOQA
|
|
||||||
|
|
||||||
from jedi._compatibility import highest_pickle_protocol # noqa: E402
|
|
||||||
|
|
||||||
|
# Try to import jedi/parso.
|
||||||
|
sys.meta_path.insert(0, _ExactImporter(_get_paths()))
|
||||||
|
from jedi.inference.compiled import subprocess # noqa: E402
|
||||||
|
sys.meta_path.pop(0)
|
||||||
|
|
||||||
# Retrieve the pickle protocol.
|
# Retrieve the pickle protocol.
|
||||||
host_sys_version = [int(x) for x in sys.argv[2].split('.')]
|
host_sys_version = [int(x) for x in sys.argv[2].split('.')]
|
||||||
pickle_protocol = highest_pickle_protocol([sys.version_info, host_sys_version])
|
|
||||||
# And finally start the client.
|
# And finally start the client.
|
||||||
subprocess.Listener(pickle_protocol=pickle_protocol).listen()
|
subprocess.Listener().listen()
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
from __future__ import print_function
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import inspect
|
||||||
|
import importlib
|
||||||
|
import warnings
|
||||||
|
from pathlib import Path
|
||||||
|
from zipimport import zipimporter
|
||||||
|
from importlib.machinery import all_suffixes
|
||||||
|
|
||||||
from jedi._compatibility import find_module, cast_path, force_unicode, \
|
from jedi._compatibility import cast_path
|
||||||
iter_modules, all_suffixes
|
|
||||||
from jedi.inference.compiled import access
|
from jedi.inference.compiled import access
|
||||||
|
from jedi import debug
|
||||||
from jedi import parser_utils
|
from jedi import parser_utils
|
||||||
|
from jedi.file_io import KnownContentFileIO, ZipFileIO
|
||||||
|
|
||||||
|
|
||||||
def get_sys_path():
|
def get_sys_path():
|
||||||
@@ -32,7 +38,7 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
|
|||||||
if sys_path is not None:
|
if sys_path is not None:
|
||||||
sys.path, temp = sys_path, sys.path
|
sys.path, temp = sys_path, sys.path
|
||||||
try:
|
try:
|
||||||
return find_module(full_name=full_name, **kwargs)
|
return _find_module(full_name=full_name, **kwargs)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return None, None
|
return None, None
|
||||||
finally:
|
finally:
|
||||||
@@ -40,15 +46,8 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
|
|||||||
sys.path = temp
|
sys.path = temp
|
||||||
|
|
||||||
|
|
||||||
def list_module_names(inference_state, search_path):
|
|
||||||
return [
|
|
||||||
force_unicode(name)
|
|
||||||
for module_loader, name, is_pkg in iter_modules(search_path)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_builtin_module_names(inference_state):
|
def get_builtin_module_names(inference_state):
|
||||||
return list(map(force_unicode, sys.builtin_module_names))
|
return sys.builtin_module_names
|
||||||
|
|
||||||
|
|
||||||
def _test_raise_error(inference_state, exception_type):
|
def _test_raise_error(inference_state, exception_type):
|
||||||
@@ -84,3 +83,155 @@ def _get_init_path(directory_path):
|
|||||||
|
|
||||||
def safe_literal_eval(inference_state, value):
|
def safe_literal_eval(inference_state, value):
|
||||||
return parser_utils.safe_literal_eval(value)
|
return parser_utils.safe_literal_eval(value)
|
||||||
|
|
||||||
|
|
||||||
|
def iter_module_names(*args, **kwargs):
|
||||||
|
return list(_iter_module_names(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_module_names(inference_state, paths):
|
||||||
|
# Python modules/packages
|
||||||
|
for path in paths:
|
||||||
|
try:
|
||||||
|
dirs = os.scandir(path)
|
||||||
|
except OSError:
|
||||||
|
# The file might not exist or reading it might lead to an error.
|
||||||
|
debug.warning("Not possible to list directory: %s", path)
|
||||||
|
continue
|
||||||
|
for dir_entry in dirs:
|
||||||
|
name = dir_entry.name
|
||||||
|
# First Namespaces then modules/stubs
|
||||||
|
if dir_entry.is_dir():
|
||||||
|
# pycache is obviously not an interesting namespace. Also the
|
||||||
|
# name must be a valid identifier.
|
||||||
|
if name != '__pycache__' and name.isidentifier():
|
||||||
|
yield name
|
||||||
|
else:
|
||||||
|
if name.endswith('.pyi'): # Stub files
|
||||||
|
modname = name[:-4]
|
||||||
|
else:
|
||||||
|
modname = inspect.getmodulename(name)
|
||||||
|
|
||||||
|
if modname and '.' not in modname:
|
||||||
|
if modname != '__init__':
|
||||||
|
yield modname
|
||||||
|
|
||||||
|
|
||||||
|
def _find_module(string, path=None, full_name=None, is_global_search=True):
|
||||||
|
"""
|
||||||
|
Provides information about a module.
|
||||||
|
|
||||||
|
This function isolates the differences in importing libraries introduced with
|
||||||
|
python 3.3 on; it gets a module name and optionally a path. It will return a
|
||||||
|
tuple containin an open file for the module (if not builtin), the filename
|
||||||
|
or the name of the module if it is a builtin one and a boolean indicating
|
||||||
|
if the module is contained in a package.
|
||||||
|
"""
|
||||||
|
spec = None
|
||||||
|
loader = None
|
||||||
|
|
||||||
|
for finder in sys.meta_path:
|
||||||
|
if is_global_search and finder != importlib.machinery.PathFinder:
|
||||||
|
p = None
|
||||||
|
else:
|
||||||
|
p = path
|
||||||
|
try:
|
||||||
|
find_spec = finder.find_spec
|
||||||
|
except AttributeError:
|
||||||
|
# These are old-school clases that still have a different API, just
|
||||||
|
# ignore those.
|
||||||
|
continue
|
||||||
|
|
||||||
|
spec = find_spec(string, p)
|
||||||
|
if spec is not None:
|
||||||
|
loader = spec.loader
|
||||||
|
if loader is None and not spec.has_location:
|
||||||
|
# This is a namespace package.
|
||||||
|
full_name = string if not path else full_name
|
||||||
|
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
|
||||||
|
return implicit_ns_info, True
|
||||||
|
break
|
||||||
|
|
||||||
|
return _find_module_py33(string, path, loader)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
|
||||||
|
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
|
||||||
|
|
||||||
|
if loader is None and path is None: # Fallback to find builtins
|
||||||
|
try:
|
||||||
|
with warnings.catch_warnings(record=True):
|
||||||
|
# Mute "DeprecationWarning: Use importlib.util.find_spec()
|
||||||
|
# instead." While we should replace that in the future, it's
|
||||||
|
# probably good to wait until we deprecate Python 3.3, since
|
||||||
|
# it was added in Python 3.4 and find_loader hasn't been
|
||||||
|
# removed in 3.6.
|
||||||
|
loader = importlib.find_loader(string)
|
||||||
|
except ValueError as e:
|
||||||
|
# See #491. Importlib might raise a ValueError, to avoid this, we
|
||||||
|
# just raise an ImportError to fix the issue.
|
||||||
|
raise ImportError("Originally " + repr(e))
|
||||||
|
|
||||||
|
if loader is None:
|
||||||
|
raise ImportError("Couldn't find a loader for {}".format(string))
|
||||||
|
|
||||||
|
return _from_loader(loader, string)
|
||||||
|
|
||||||
|
|
||||||
|
def _from_loader(loader, string):
|
||||||
|
try:
|
||||||
|
is_package_method = loader.is_package
|
||||||
|
except AttributeError:
|
||||||
|
is_package = False
|
||||||
|
else:
|
||||||
|
is_package = is_package_method(string)
|
||||||
|
try:
|
||||||
|
get_filename = loader.get_filename
|
||||||
|
except AttributeError:
|
||||||
|
return None, is_package
|
||||||
|
else:
|
||||||
|
module_path = cast_path(get_filename(string))
|
||||||
|
|
||||||
|
# To avoid unicode and read bytes, "overwrite" loader.get_source if
|
||||||
|
# possible.
|
||||||
|
try:
|
||||||
|
f = type(loader).get_source
|
||||||
|
except AttributeError:
|
||||||
|
raise ImportError("get_source was not defined on loader")
|
||||||
|
|
||||||
|
if f is not importlib.machinery.SourceFileLoader.get_source:
|
||||||
|
# Unfortunately we are reading unicode here, not bytes.
|
||||||
|
# It seems hard to get bytes, because the zip importer
|
||||||
|
# logic just unpacks the zip file and returns a file descriptor
|
||||||
|
# that we cannot as easily access. Therefore we just read it as
|
||||||
|
# a string in the cases where get_source was overwritten.
|
||||||
|
code = loader.get_source(string)
|
||||||
|
else:
|
||||||
|
code = _get_source(loader, string)
|
||||||
|
|
||||||
|
if code is None:
|
||||||
|
return None, is_package
|
||||||
|
if isinstance(loader, zipimporter):
|
||||||
|
return ZipFileIO(module_path, code, Path(cast_path(loader.archive))), is_package
|
||||||
|
|
||||||
|
return KnownContentFileIO(module_path, code), is_package
|
||||||
|
|
||||||
|
|
||||||
|
def _get_source(loader, fullname):
|
||||||
|
"""
|
||||||
|
This method is here as a replacement for SourceLoader.get_source. That
|
||||||
|
method returns unicode, but we prefer bytes.
|
||||||
|
"""
|
||||||
|
path = loader.get_filename(fullname)
|
||||||
|
try:
|
||||||
|
return loader.get_data(path)
|
||||||
|
except OSError:
|
||||||
|
raise ImportError('source not available through get_data()',
|
||||||
|
name=fullname)
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitNSInfo:
|
||||||
|
"""Stores information returned from an implicit namespace spec"""
|
||||||
|
def __init__(self, name, paths):
|
||||||
|
self.name = name
|
||||||
|
self.paths = paths
|
||||||
@@ -3,10 +3,12 @@ Imitate the parser representation.
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from inspect import Parameter
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
from jedi._compatibility import force_unicode, Parameter, cast_path
|
from jedi._compatibility import cast_path
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.inference.filters import AbstractFilter
|
from jedi.inference.filters import AbstractFilter
|
||||||
from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \
|
from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \
|
||||||
@@ -20,8 +22,8 @@ from jedi.inference.signature import BuiltinSignature
|
|||||||
from jedi.inference.context import CompiledContext, CompiledModuleContext
|
from jedi.inference.context import CompiledContext, CompiledModuleContext
|
||||||
|
|
||||||
|
|
||||||
class CheckAttribute(object):
|
class CheckAttribute:
|
||||||
"""Raises an AttributeError if the attribute X isn't available."""
|
"""Raises :exc:`AttributeError` if the attribute X is not available."""
|
||||||
def __init__(self, check_name=None):
|
def __init__(self, check_name=None):
|
||||||
# Remove the py in front of e.g. py__call__.
|
# Remove the py in front of e.g. py__call__.
|
||||||
self.check_name = check_name
|
self.check_name = check_name
|
||||||
@@ -29,7 +31,7 @@ class CheckAttribute(object):
|
|||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
self.func = func
|
self.func = func
|
||||||
if self.check_name is None:
|
if self.check_name is None:
|
||||||
self.check_name = force_unicode(func.__name__[2:])
|
self.check_name = func.__name__[2:]
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
@@ -43,7 +45,7 @@ class CheckAttribute(object):
|
|||||||
|
|
||||||
class CompiledValue(Value):
|
class CompiledValue(Value):
|
||||||
def __init__(self, inference_state, access_handle, parent_context=None):
|
def __init__(self, inference_state, access_handle, parent_context=None):
|
||||||
super(CompiledValue, self).__init__(inference_state, parent_context)
|
super().__init__(inference_state, parent_context)
|
||||||
self.access_handle = access_handle
|
self.access_handle = access_handle
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
@@ -56,9 +58,9 @@ class CompiledValue(Value):
|
|||||||
).execute_annotation()
|
).execute_annotation()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.access_handle.getattr_paths(u'__call__')
|
self.access_handle.getattr_paths('__call__')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return super(CompiledValue, self).py__call__(arguments)
|
return super().py__call__(arguments)
|
||||||
else:
|
else:
|
||||||
if self.access_handle.is_class():
|
if self.access_handle.is_class():
|
||||||
from jedi.inference.value import CompiledInstance
|
from jedi.inference.value import CompiledInstance
|
||||||
@@ -163,7 +165,7 @@ class CompiledValue(Value):
|
|||||||
try:
|
try:
|
||||||
access = self.access_handle.py__simple_getitem__(index)
|
access = self.access_handle.py__simple_getitem__(index)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return super(CompiledValue, self).py__simple_getitem__(index)
|
return super().py__simple_getitem__(index)
|
||||||
if access is None:
|
if access is None:
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|
||||||
@@ -174,20 +176,15 @@ class CompiledValue(Value):
|
|||||||
if all_access_paths is None:
|
if all_access_paths is None:
|
||||||
# This means basically that no __getitem__ has been defined on this
|
# This means basically that no __getitem__ has been defined on this
|
||||||
# object.
|
# object.
|
||||||
return super(CompiledValue, self).py__getitem__(index_value_set, contextualized_node)
|
return super().py__getitem__(index_value_set, contextualized_node)
|
||||||
return ValueSet(
|
return ValueSet(
|
||||||
create_from_access_path(self.inference_state, access)
|
create_from_access_path(self.inference_state, access)
|
||||||
for access in all_access_paths
|
for access in all_access_paths
|
||||||
)
|
)
|
||||||
|
|
||||||
def py__iter__(self, contextualized_node=None):
|
def py__iter__(self, contextualized_node=None):
|
||||||
# Python iterators are a bit strange, because there's no need for
|
|
||||||
# the __iter__ function as long as __getitem__ is defined (it will
|
|
||||||
# just start with __getitem__(0). This is especially true for
|
|
||||||
# Python 2 strings, where `str.__iter__` is not even defined.
|
|
||||||
if not self.access_handle.has_iter():
|
if not self.access_handle.has_iter():
|
||||||
for x in super(CompiledValue, self).py__iter__(contextualized_node):
|
yield from super().py__iter__(contextualized_node)
|
||||||
yield x
|
|
||||||
|
|
||||||
access_path_list = self.access_handle.py__iter__list()
|
access_path_list = self.access_handle.py__iter__list()
|
||||||
if access_path_list is None:
|
if access_path_list is None:
|
||||||
@@ -222,10 +219,8 @@ class CompiledValue(Value):
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
bltn_obj = builtin_from_name(self.inference_state, name)
|
bltn_obj = builtin_from_name(self.inference_state, name)
|
||||||
for result in self.inference_state.execute(bltn_obj, params):
|
yield from self.inference_state.execute(bltn_obj, params)
|
||||||
yield result
|
yield from docstrings.infer_return_types(self)
|
||||||
for type_ in docstrings.infer_return_types(self):
|
|
||||||
yield type_
|
|
||||||
|
|
||||||
def get_safe_value(self, default=_sentinel):
|
def get_safe_value(self, default=_sentinel):
|
||||||
try:
|
try:
|
||||||
@@ -264,7 +259,7 @@ class CompiledValue(Value):
|
|||||||
v.with_generics(arguments)
|
v.with_generics(arguments)
|
||||||
for v in self.inference_state.typing_module.py__getattribute__(name)
|
for v in self.inference_state.typing_module.py__getattribute__(name)
|
||||||
]).execute_annotation()
|
]).execute_annotation()
|
||||||
return super(CompiledValue, self).execute_annotation()
|
return super().execute_annotation()
|
||||||
|
|
||||||
def negate(self):
|
def negate(self):
|
||||||
return create_from_access_path(self.inference_state, self.access_handle.negate())
|
return create_from_access_path(self.inference_state, self.access_handle.negate())
|
||||||
@@ -285,6 +280,11 @@ class CompiledValue(Value):
|
|||||||
for k in self.access_handle.get_key_paths()
|
for k in self.access_handle.get_key_paths()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_type_hint(self, add_class_info=True):
|
||||||
|
if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
|
||||||
|
return 'None'
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CompiledModule(CompiledValue):
|
class CompiledModule(CompiledValue):
|
||||||
file_io = None # For modules
|
file_io = None # For modules
|
||||||
@@ -310,7 +310,10 @@ class CompiledModule(CompiledValue):
|
|||||||
return tuple(name.split('.'))
|
return tuple(name.split('.'))
|
||||||
|
|
||||||
def py__file__(self):
|
def py__file__(self):
|
||||||
return cast_path(self.access_handle.py__file__())
|
path = cast_path(self.access_handle.py__file__())
|
||||||
|
if path is None:
|
||||||
|
return None
|
||||||
|
return Path(path)
|
||||||
|
|
||||||
|
|
||||||
class CompiledName(AbstractNameDefinition):
|
class CompiledName(AbstractNameDefinition):
|
||||||
@@ -321,8 +324,7 @@ class CompiledName(AbstractNameDefinition):
|
|||||||
self.string_name = name
|
self.string_name = name
|
||||||
|
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
value, = self.infer()
|
return self.infer_compiled_value().py__doc__()
|
||||||
return value.py__doc__()
|
|
||||||
|
|
||||||
def _get_qualified_names(self):
|
def _get_qualified_names(self):
|
||||||
parent_qualified_names = self.parent_context.get_qualified_names()
|
parent_qualified_names = self.parent_context.get_qualified_names()
|
||||||
@@ -346,16 +348,12 @@ class CompiledName(AbstractNameDefinition):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def api_type(self):
|
def api_type(self):
|
||||||
api = self.infer()
|
return self.infer_compiled_value().api_type
|
||||||
# If we can't find the type, assume it is an instance variable
|
|
||||||
if not api:
|
|
||||||
return "instance"
|
|
||||||
return next(iter(api)).api_type
|
|
||||||
|
|
||||||
@memoize_method
|
|
||||||
def infer(self):
|
def infer(self):
|
||||||
return ValueSet([self.infer_compiled_value()])
|
return ValueSet([self.infer_compiled_value()])
|
||||||
|
|
||||||
|
@memoize_method
|
||||||
def infer_compiled_value(self):
|
def infer_compiled_value(self):
|
||||||
return create_from_name(self._inference_state, self._parent_value, self.string_name)
|
return create_from_name(self._inference_state, self._parent_value, self.string_name)
|
||||||
|
|
||||||
@@ -451,9 +449,6 @@ class CompiledValueFilter(AbstractFilter):
|
|||||||
"""
|
"""
|
||||||
To remove quite a few access calls we introduced the callback here.
|
To remove quite a few access calls we introduced the callback here.
|
||||||
"""
|
"""
|
||||||
# Always use unicode objects in Python 2 from here.
|
|
||||||
name = force_unicode(name)
|
|
||||||
|
|
||||||
if self._inference_state.allow_descriptor_getattr:
|
if self._inference_state.allow_descriptor_getattr:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -497,7 +492,7 @@ class CompiledValueFilter(AbstractFilter):
|
|||||||
|
|
||||||
# ``dir`` doesn't include the type names.
|
# ``dir`` doesn't include the type names.
|
||||||
if not self.is_instance and needs_type_completions:
|
if not self.is_instance and needs_type_completions:
|
||||||
for filter in builtin_from_name(self._inference_state, u'type').get_filters():
|
for filter in builtin_from_name(self._inference_state, 'type').get_filters():
|
||||||
names += filter.values()
|
names += filter.values()
|
||||||
return names
|
return names
|
||||||
|
|
||||||
@@ -513,11 +508,11 @@ class CompiledValueFilter(AbstractFilter):
|
|||||||
|
|
||||||
|
|
||||||
docstr_defaults = {
|
docstr_defaults = {
|
||||||
'floating point number': u'float',
|
'floating point number': 'float',
|
||||||
'character': u'str',
|
'character': 'str',
|
||||||
'integer': u'int',
|
'integer': 'int',
|
||||||
'dictionary': u'dict',
|
'dictionary': 'dict',
|
||||||
'string': u'str',
|
'string': 'str',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -529,7 +524,6 @@ def _parse_function_doc(doc):
|
|||||||
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
|
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
|
||||||
TODO docstrings like 'tuple of integers'
|
TODO docstrings like 'tuple of integers'
|
||||||
"""
|
"""
|
||||||
doc = force_unicode(doc)
|
|
||||||
# parse round parentheses: def func(a, (b,c))
|
# parse round parentheses: def func(a, (b,c))
|
||||||
try:
|
try:
|
||||||
count = 0
|
count = 0
|
||||||
@@ -548,7 +542,7 @@ def _parse_function_doc(doc):
|
|||||||
# UnboundLocalError for undefined end in last line
|
# UnboundLocalError for undefined end in last line
|
||||||
debug.dbg('no brackets found - no param')
|
debug.dbg('no brackets found - no param')
|
||||||
end = 0
|
end = 0
|
||||||
param_str = u''
|
param_str = ''
|
||||||
else:
|
else:
|
||||||
# remove square brackets, that show an optional param ( = None)
|
# remove square brackets, that show an optional param ( = None)
|
||||||
def change_options(m):
|
def change_options(m):
|
||||||
@@ -566,9 +560,9 @@ def _parse_function_doc(doc):
|
|||||||
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
|
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
|
||||||
|
|
||||||
# parse return value
|
# parse return value
|
||||||
r = re.search(u'-[>-]* ', doc[end:end + 7])
|
r = re.search('-[>-]* ', doc[end:end + 7])
|
||||||
if r is None:
|
if r is None:
|
||||||
ret = u''
|
ret = ''
|
||||||
else:
|
else:
|
||||||
index = end + r.end()
|
index = end + r.end()
|
||||||
# get result type, which can contain newlines
|
# get result type, which can contain newlines
|
||||||
@@ -584,9 +578,6 @@ def _parse_function_doc(doc):
|
|||||||
|
|
||||||
def create_from_name(inference_state, compiled_value, name):
|
def create_from_name(inference_state, compiled_value, name):
|
||||||
access_paths = compiled_value.access_handle.getattr_paths(name, default=None)
|
access_paths = compiled_value.access_handle.getattr_paths(name, default=None)
|
||||||
parent_context = compiled_value
|
|
||||||
if parent_context.is_class():
|
|
||||||
parent_context = parent_context.parent_context
|
|
||||||
|
|
||||||
value = None
|
value = None
|
||||||
for access_path in access_paths:
|
for access_path in access_paths:
|
||||||
|
|||||||
+27
-18
@@ -13,7 +13,7 @@ from jedi import debug
|
|||||||
from jedi import parser_utils
|
from jedi import parser_utils
|
||||||
|
|
||||||
|
|
||||||
class AbstractContext(object):
|
class AbstractContext:
|
||||||
# Must be defined: inference_state and tree_node and parent_context as an attribute/property
|
# Must be defined: inference_state and tree_node and parent_context as an attribute/property
|
||||||
|
|
||||||
def __init__(self, inference_state):
|
def __init__(self, inference_state):
|
||||||
@@ -129,6 +129,9 @@ class AbstractContext(object):
|
|||||||
def is_compiled(self):
|
def is_compiled(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_bound_method(self):
|
||||||
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def py__name__(self):
|
def py__name__(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -161,7 +164,7 @@ class ValueContext(AbstractContext):
|
|||||||
Should be defined, otherwise the API returns empty types.
|
Should be defined, otherwise the API returns empty types.
|
||||||
"""
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
super(ValueContext, self).__init__(value.inference_state)
|
super().__init__(value.inference_state)
|
||||||
self._value = value
|
self._value = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -190,6 +193,9 @@ class ValueContext(AbstractContext):
|
|||||||
def is_compiled(self):
|
def is_compiled(self):
|
||||||
return self._value.is_compiled()
|
return self._value.is_compiled()
|
||||||
|
|
||||||
|
def is_bound_method(self):
|
||||||
|
return self._value.is_bound_method()
|
||||||
|
|
||||||
def py__name__(self):
|
def py__name__(self):
|
||||||
return self._value.py__name__()
|
return self._value.py__name__()
|
||||||
|
|
||||||
@@ -210,7 +216,7 @@ class ValueContext(AbstractContext):
|
|||||||
return '%s(%s)' % (self.__class__.__name__, self._value)
|
return '%s(%s)' % (self.__class__.__name__, self._value)
|
||||||
|
|
||||||
|
|
||||||
class TreeContextMixin(object):
|
class TreeContextMixin:
|
||||||
def infer_node(self, node):
|
def infer_node(self, node):
|
||||||
from jedi.inference.syntax_tree import infer_node
|
from jedi.inference.syntax_tree import infer_node
|
||||||
return infer_node(self, node)
|
return infer_node(self, node)
|
||||||
@@ -249,8 +255,7 @@ class TreeContextMixin(object):
|
|||||||
if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
|
if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
|
||||||
return self.create_value(scope_node).as_context()
|
return self.create_value(scope_node).as_context()
|
||||||
elif scope_node.type in ('comp_for', 'sync_comp_for'):
|
elif scope_node.type in ('comp_for', 'sync_comp_for'):
|
||||||
parent_scope = parser_utils.get_parent_scope(scope_node)
|
parent_context = from_scope_node(parent_scope(scope_node.parent))
|
||||||
parent_context = from_scope_node(parent_scope)
|
|
||||||
if node.start_pos >= scope_node.children[-1].start_pos:
|
if node.start_pos >= scope_node.children[-1].start_pos:
|
||||||
return parent_context
|
return parent_context
|
||||||
return CompForContext(parent_context, scope_node)
|
return CompForContext(parent_context, scope_node)
|
||||||
@@ -308,7 +313,7 @@ class ModuleContext(TreeContextMixin, ValueContext):
|
|||||||
def get_filters(self, until_position=None, origin_scope=None):
|
def get_filters(self, until_position=None, origin_scope=None):
|
||||||
filters = self._value.get_filters(origin_scope)
|
filters = self._value.get_filters(origin_scope)
|
||||||
# Skip the first filter and replace it.
|
# Skip the first filter and replace it.
|
||||||
next(filters)
|
next(filters, None)
|
||||||
yield MergedFilter(
|
yield MergedFilter(
|
||||||
ParserTreeFilter(
|
ParserTreeFilter(
|
||||||
parent_context=self,
|
parent_context=self,
|
||||||
@@ -317,8 +322,7 @@ class ModuleContext(TreeContextMixin, ValueContext):
|
|||||||
),
|
),
|
||||||
self.get_global_filter(),
|
self.get_global_filter(),
|
||||||
)
|
)
|
||||||
for f in filters: # Python 2...
|
yield from filters
|
||||||
yield f
|
|
||||||
|
|
||||||
def get_global_filter(self):
|
def get_global_filter(self):
|
||||||
return GlobalNameFilter(self, self.tree_node)
|
return GlobalNameFilter(self, self.tree_node)
|
||||||
@@ -347,6 +351,10 @@ class NamespaceContext(TreeContextMixin, ValueContext):
|
|||||||
def get_value(self):
|
def get_value(self):
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def string_names(self):
|
||||||
|
return self._value.string_names
|
||||||
|
|
||||||
def py__file__(self):
|
def py__file__(self):
|
||||||
return self._value.py__file__()
|
return self._value.py__file__()
|
||||||
|
|
||||||
@@ -365,7 +373,7 @@ class ClassContext(TreeContextMixin, ValueContext):
|
|||||||
|
|
||||||
class CompForContext(TreeContextMixin, AbstractContext):
|
class CompForContext(TreeContextMixin, AbstractContext):
|
||||||
def __init__(self, parent_context, comp_for):
|
def __init__(self, parent_context, comp_for):
|
||||||
super(CompForContext, self).__init__(parent_context.inference_state)
|
super().__init__(parent_context.inference_state)
|
||||||
self.tree_node = comp_for
|
self.tree_node = comp_for
|
||||||
self.parent_context = parent_context
|
self.parent_context = parent_context
|
||||||
|
|
||||||
@@ -429,13 +437,12 @@ def get_global_filters(context, until_position, origin_scope):
|
|||||||
For global name lookups. The filters will handle name resolution
|
For global name lookups. The filters will handle name resolution
|
||||||
themselves, but here we gather possible filters downwards.
|
themselves, but here we gather possible filters downwards.
|
||||||
|
|
||||||
>>> from jedi._compatibility import u, no_unicode_pprint
|
|
||||||
>>> from jedi import Script
|
>>> from jedi import Script
|
||||||
>>> script = Script(u('''
|
>>> script = Script('''
|
||||||
... x = ['a', 'b', 'c']
|
... x = ['a', 'b', 'c']
|
||||||
... def func():
|
... def func():
|
||||||
... y = None
|
... y = None
|
||||||
... '''))
|
... ''')
|
||||||
>>> module_node = script._module_node
|
>>> module_node = script._module_node
|
||||||
>>> scope = next(module_node.iter_funcdefs())
|
>>> scope = next(module_node.iter_funcdefs())
|
||||||
>>> scope
|
>>> scope
|
||||||
@@ -445,7 +452,7 @@ def get_global_filters(context, until_position, origin_scope):
|
|||||||
|
|
||||||
First we get the names from the function scope.
|
First we get the names from the function scope.
|
||||||
|
|
||||||
>>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS
|
>>> print(filters[0]) # doctest: +ELLIPSIS
|
||||||
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
|
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
|
||||||
>>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE
|
>>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE
|
||||||
['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
|
['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
|
||||||
@@ -474,15 +481,17 @@ def get_global_filters(context, until_position, origin_scope):
|
|||||||
from jedi.inference.value.function import BaseFunctionExecutionContext
|
from jedi.inference.value.function import BaseFunctionExecutionContext
|
||||||
while context is not None:
|
while context is not None:
|
||||||
# Names in methods cannot be resolved within the class.
|
# Names in methods cannot be resolved within the class.
|
||||||
for filter in context.get_filters(
|
yield from context.get_filters(
|
||||||
until_position=until_position,
|
until_position=until_position,
|
||||||
origin_scope=origin_scope):
|
origin_scope=origin_scope
|
||||||
yield filter
|
)
|
||||||
if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)):
|
if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)):
|
||||||
# The position should be reset if the current scope is a function.
|
# The position should be reset if the current scope is a function.
|
||||||
until_position = None
|
until_position = None
|
||||||
|
|
||||||
context = context.parent_context
|
context = context.parent_context
|
||||||
|
|
||||||
|
b = next(base_context.inference_state.builtins_module.get_filters(), None)
|
||||||
|
assert b is not None
|
||||||
# Add builtins to the global scope.
|
# Add builtins to the global scope.
|
||||||
yield next(base_context.inference_state.builtins_module.get_filters())
|
yield b
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Docstrings are another source of information for functions and classes.
|
Docstrings are another source of information for functions and classes.
|
||||||
:mod:`jedi.inference.dynamic` tries to find all executions of functions, while
|
:mod:`jedi.inference.dynamic_params` tries to find all executions of functions,
|
||||||
the docstring parsing is much easier. There are three different types of
|
while the docstring parsing is much easier. There are three different types of
|
||||||
docstrings that |jedi| understands:
|
docstrings that |jedi| understands:
|
||||||
|
|
||||||
- `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_
|
- `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_
|
||||||
@@ -21,9 +21,8 @@ from textwrap import dedent
|
|||||||
|
|
||||||
from parso import parse, ParserSyntaxError
|
from parso import parse, ParserSyntaxError
|
||||||
|
|
||||||
from jedi._compatibility import u
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.utils import indent_block
|
from jedi.common import indent_block
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.base_value import iterator_to_value_set, ValueSet, \
|
from jedi.inference.base_value import iterator_to_value_set, ValueSet, \
|
||||||
NO_VALUES
|
NO_VALUES
|
||||||
@@ -51,7 +50,7 @@ def _get_numpy_doc_string_cls():
|
|||||||
global _numpy_doc_string_cache
|
global _numpy_doc_string_cache
|
||||||
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
||||||
raise _numpy_doc_string_cache
|
raise _numpy_doc_string_cache
|
||||||
from numpydoc.docscrape import NumpyDocString
|
from numpydoc.docscrape import NumpyDocString # type: ignore[import]
|
||||||
_numpy_doc_string_cache = NumpyDocString
|
_numpy_doc_string_cache = NumpyDocString
|
||||||
return _numpy_doc_string_cache
|
return _numpy_doc_string_cache
|
||||||
|
|
||||||
@@ -96,8 +95,7 @@ def _search_return_in_numpydocstr(docstr):
|
|||||||
# Return names are optional and if so the type is in the name
|
# Return names are optional and if so the type is in the name
|
||||||
if not r_type:
|
if not r_type:
|
||||||
r_type = r_name
|
r_type = r_name
|
||||||
for type_ in _expand_typestr(r_type):
|
yield from _expand_typestr(r_type)
|
||||||
yield type_
|
|
||||||
|
|
||||||
|
|
||||||
def _expand_typestr(type_str):
|
def _expand_typestr(type_str):
|
||||||
@@ -115,7 +113,7 @@ def _expand_typestr(type_str):
|
|||||||
elif type_str.startswith('{'):
|
elif type_str.startswith('{'):
|
||||||
node = parse(type_str, version='3.7').children[0]
|
node = parse(type_str, version='3.7').children[0]
|
||||||
if node.type == 'atom':
|
if node.type == 'atom':
|
||||||
for leaf in node.children[1].children:
|
for leaf in getattr(node.children[1], "children", []):
|
||||||
if leaf.type == 'number':
|
if leaf.type == 'number':
|
||||||
if '.' in leaf.value:
|
if '.' in leaf.value:
|
||||||
yield 'float'
|
yield 'float'
|
||||||
@@ -184,7 +182,7 @@ def _strip_rst_role(type_str):
|
|||||||
|
|
||||||
|
|
||||||
def _infer_for_statement_string(module_context, string):
|
def _infer_for_statement_string(module_context, string):
|
||||||
code = dedent(u("""
|
code = dedent("""
|
||||||
def pseudo_docstring_stuff():
|
def pseudo_docstring_stuff():
|
||||||
'''
|
'''
|
||||||
Create a pseudo function for docstring statements.
|
Create a pseudo function for docstring statements.
|
||||||
@@ -192,7 +190,7 @@ def _infer_for_statement_string(module_context, string):
|
|||||||
is still a function.
|
is still a function.
|
||||||
'''
|
'''
|
||||||
{}
|
{}
|
||||||
"""))
|
""")
|
||||||
if string is None:
|
if string is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -201,11 +199,8 @@ def _infer_for_statement_string(module_context, string):
|
|||||||
# (e.g., 'threading' in 'threading.Thread').
|
# (e.g., 'threading' in 'threading.Thread').
|
||||||
string = 'import %s\n' % element + string
|
string = 'import %s\n' % element + string
|
||||||
|
|
||||||
# Take the default grammar here, if we load the Python 2.7 grammar here, it
|
|
||||||
# will be impossible to use `...` (Ellipsis) as a token. Docstring types
|
|
||||||
# don't need to conform with the current grammar.
|
|
||||||
debug.dbg('Parse docstring code %s', string, color='BLUE')
|
debug.dbg('Parse docstring code %s', string, color='BLUE')
|
||||||
grammar = module_context.inference_state.latest_grammar
|
grammar = module_context.inference_state.grammar
|
||||||
try:
|
try:
|
||||||
module = grammar.parse(code.format(indent_block(string)), error_recovery=False)
|
module = grammar.parse(code.format(indent_block(string)), error_recovery=False)
|
||||||
except ParserSyntaxError:
|
except ParserSyntaxError:
|
||||||
@@ -299,9 +294,7 @@ def infer_return_types(function_value):
|
|||||||
if match:
|
if match:
|
||||||
yield _strip_rst_role(match.group(1))
|
yield _strip_rst_role(match.group(1))
|
||||||
# Check for numpy style return hint
|
# Check for numpy style return hint
|
||||||
for type_ in _search_return_in_numpydocstr(code):
|
yield from _search_return_in_numpydocstr(code)
|
||||||
yield type_
|
|
||||||
|
|
||||||
for type_str in search_return_in_docstr(function_value.py__doc__()):
|
for type_str in search_return_in_docstr(function_value.py__doc__()):
|
||||||
for value in _infer_for_statement_string(function_value.get_root_context(), type_str):
|
yield from _infer_for_statement_string(function_value.get_root_context(), type_str)
|
||||||
yield value
|
|
||||||
@@ -48,7 +48,6 @@ def _avoid_recursions(func):
|
|||||||
finally:
|
finally:
|
||||||
inf.dynamic_params_depth -= 1
|
inf.dynamic_params_depth -= 1
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
return
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -216,12 +215,10 @@ def _check_name_for_execution(inference_state, context, compare_node, name, trai
|
|||||||
for name, trailer in potential_nodes:
|
for name, trailer in potential_nodes:
|
||||||
if value_node.start_pos < name.start_pos < value_node.end_pos:
|
if value_node.start_pos < name.start_pos < value_node.end_pos:
|
||||||
random_context = execution_context.create_context(name)
|
random_context = execution_context.create_context(name)
|
||||||
iterator = _check_name_for_execution(
|
yield from _check_name_for_execution(
|
||||||
inference_state,
|
inference_state,
|
||||||
random_context,
|
random_context,
|
||||||
compare_node,
|
compare_node,
|
||||||
name,
|
name,
|
||||||
trailer
|
trailer
|
||||||
)
|
)
|
||||||
for arguments in iterator:
|
|
||||||
yield arguments
|
|
||||||
+31
-38
@@ -3,23 +3,25 @@ Filters are objects that you can use to filter names in different scopes. They
|
|||||||
are needed for name resolution.
|
are needed for name resolution.
|
||||||
"""
|
"""
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from typing import List, MutableMapping, Type
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from parso.tree import search_ancestor
|
from parso.tree import search_ancestor
|
||||||
|
from parso.python.tree import Name, UsedNamesMapping
|
||||||
|
|
||||||
from jedi._compatibility import use_metaclass
|
|
||||||
from jedi.inference import flow_analysis
|
from jedi.inference import flow_analysis
|
||||||
from jedi.inference.base_value import ValueSet, ValueWrapper, \
|
from jedi.inference.base_value import ValueSet, ValueWrapper, \
|
||||||
LazyValueWrapper
|
LazyValueWrapper
|
||||||
from jedi.parser_utils import get_cached_parent_scope
|
from jedi.parser_utils import get_cached_parent_scope
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
from jedi.inference.names import TreeNameDefinition, ParamName, \
|
from jedi.inference.names import TreeNameDefinition, ParamName, \
|
||||||
AnonymousParamName, AbstractNameDefinition
|
AnonymousParamName, AbstractNameDefinition, NameWrapper
|
||||||
|
|
||||||
|
_definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]]
|
||||||
_definition_name_cache = weakref.WeakKeyDictionary()
|
_definition_name_cache = weakref.WeakKeyDictionary()
|
||||||
|
|
||||||
|
|
||||||
class AbstractFilter(object):
|
class AbstractFilter:
|
||||||
_until_position = None
|
_until_position = None
|
||||||
|
|
||||||
def _filter(self, names):
|
def _filter(self, names):
|
||||||
@@ -36,8 +38,8 @@ class AbstractFilter(object):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class FilterWrapper(object):
|
class FilterWrapper:
|
||||||
name_wrapper_class = None
|
name_wrapper_class: Type[NameWrapper]
|
||||||
|
|
||||||
def __init__(self, wrapped_filter):
|
def __init__(self, wrapped_filter):
|
||||||
self._wrapped_filter = wrapped_filter
|
self._wrapped_filter = wrapped_filter
|
||||||
@@ -77,22 +79,20 @@ class AbstractUsedNamesFilter(AbstractFilter):
|
|||||||
self._used_names = self._module_node.get_used_names()
|
self._used_names = self._module_node.get_used_names()
|
||||||
self.parent_context = parent_context
|
self.parent_context = parent_context
|
||||||
|
|
||||||
def get(self, name, **filter_kwargs):
|
def get(self, name):
|
||||||
return self._convert_names(self._filter(
|
return self._convert_names(self._filter(
|
||||||
_get_definition_names(self._used_names, name),
|
_get_definition_names(self._used_names, name),
|
||||||
**filter_kwargs
|
|
||||||
))
|
))
|
||||||
|
|
||||||
def _convert_names(self, names):
|
def _convert_names(self, names):
|
||||||
return [self.name_class(self.parent_context, name) for name in names]
|
return [self.name_class(self.parent_context, name) for name in names]
|
||||||
|
|
||||||
def values(self, **filter_kwargs):
|
def values(self):
|
||||||
return self._convert_names(
|
return self._convert_names(
|
||||||
name
|
name
|
||||||
for name_key in self._used_names
|
for name_key in self._used_names
|
||||||
for name in self._filter(
|
for name in self._filter(
|
||||||
_get_definition_names(self._used_names, name_key),
|
_get_definition_names(self._used_names, name_key),
|
||||||
**filter_kwargs
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,13 +111,13 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
|
|||||||
"""
|
"""
|
||||||
if node_context is None:
|
if node_context is None:
|
||||||
node_context = parent_context
|
node_context = parent_context
|
||||||
super(ParserTreeFilter, self).__init__(parent_context, node_context.tree_node)
|
super().__init__(parent_context, node_context.tree_node)
|
||||||
self._node_context = node_context
|
self._node_context = node_context
|
||||||
self._origin_scope = origin_scope
|
self._origin_scope = origin_scope
|
||||||
self._until_position = until_position
|
self._until_position = until_position
|
||||||
|
|
||||||
def _filter(self, names):
|
def _filter(self, names):
|
||||||
names = super(ParserTreeFilter, self)._filter(names)
|
names = super()._filter(names)
|
||||||
names = [n for n in names if self._is_name_reachable(n)]
|
names = [n for n in names if self._is_name_reachable(n)]
|
||||||
return list(self._check_flows(names))
|
return list(self._check_flows(names))
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
|
|||||||
|
|
||||||
class _FunctionExecutionFilter(ParserTreeFilter):
|
class _FunctionExecutionFilter(ParserTreeFilter):
|
||||||
def __init__(self, parent_context, function_value, until_position, origin_scope):
|
def __init__(self, parent_context, function_value, until_position, origin_scope):
|
||||||
super(_FunctionExecutionFilter, self).__init__(
|
super().__init__(
|
||||||
parent_context,
|
parent_context,
|
||||||
until_position=until_position,
|
until_position=until_position,
|
||||||
origin_scope=origin_scope,
|
origin_scope=origin_scope,
|
||||||
@@ -169,9 +169,9 @@ class _FunctionExecutionFilter(ParserTreeFilter):
|
|||||||
|
|
||||||
|
|
||||||
class FunctionExecutionFilter(_FunctionExecutionFilter):
|
class FunctionExecutionFilter(_FunctionExecutionFilter):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, arguments, **kwargs):
|
||||||
self._arguments = kwargs.pop('arguments') # Python 2
|
super().__init__(*args, **kwargs)
|
||||||
super(FunctionExecutionFilter, self).__init__(*args, **kwargs)
|
self._arguments = arguments
|
||||||
|
|
||||||
def _convert_param(self, param, name):
|
def _convert_param(self, param, name):
|
||||||
return ParamName(self._function_value, name, self._arguments)
|
return ParamName(self._function_value, name, self._arguments)
|
||||||
@@ -232,7 +232,7 @@ class DictFilter(AbstractFilter):
|
|||||||
return '<%s: for {%s}>' % (self.__class__.__name__, keys)
|
return '<%s: for {%s}>' % (self.__class__.__name__, keys)
|
||||||
|
|
||||||
|
|
||||||
class MergedFilter(object):
|
class MergedFilter:
|
||||||
def __init__(self, *filters):
|
def __init__(self, *filters):
|
||||||
self._filters = filters
|
self._filters = filters
|
||||||
|
|
||||||
@@ -248,16 +248,16 @@ class MergedFilter(object):
|
|||||||
|
|
||||||
class _BuiltinMappedMethod(ValueWrapper):
|
class _BuiltinMappedMethod(ValueWrapper):
|
||||||
"""``Generator.__next__`` ``dict.values`` methods and so on."""
|
"""``Generator.__next__`` ``dict.values`` methods and so on."""
|
||||||
api_type = u'function'
|
api_type = 'function'
|
||||||
|
|
||||||
def __init__(self, value, method, builtin_func):
|
def __init__(self, value, method, builtin_func):
|
||||||
super(_BuiltinMappedMethod, self).__init__(builtin_func)
|
super().__init__(builtin_func)
|
||||||
self._value = value
|
self._value = value
|
||||||
self._method = method
|
self._method = method
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
# TODO add TypeError if params are given/or not correct.
|
# TODO add TypeError if params are given/or not correct.
|
||||||
return self._method(self._value)
|
return self._method(self._value, arguments)
|
||||||
|
|
||||||
|
|
||||||
class SpecialMethodFilter(DictFilter):
|
class SpecialMethodFilter(DictFilter):
|
||||||
@@ -266,14 +266,9 @@ class SpecialMethodFilter(DictFilter):
|
|||||||
classes like Generator (for __next__, etc).
|
classes like Generator (for __next__, etc).
|
||||||
"""
|
"""
|
||||||
class SpecialMethodName(AbstractNameDefinition):
|
class SpecialMethodName(AbstractNameDefinition):
|
||||||
api_type = u'function'
|
api_type = 'function'
|
||||||
|
|
||||||
def __init__(self, parent_context, string_name, value, builtin_value):
|
|
||||||
callable_, python_version = value
|
|
||||||
if python_version is not None and \
|
|
||||||
python_version != parent_context.inference_state.environment.version_info.major:
|
|
||||||
raise KeyError
|
|
||||||
|
|
||||||
|
def __init__(self, parent_context, string_name, callable_, builtin_value):
|
||||||
self.parent_context = parent_context
|
self.parent_context = parent_context
|
||||||
self.string_name = string_name
|
self.string_name = string_name
|
||||||
self._callable = callable_
|
self._callable = callable_
|
||||||
@@ -295,7 +290,7 @@ class SpecialMethodFilter(DictFilter):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def __init__(self, value, dct, builtin_value):
|
def __init__(self, value, dct, builtin_value):
|
||||||
super(SpecialMethodFilter, self).__init__(dct)
|
super().__init__(dct)
|
||||||
self.value = value
|
self.value = value
|
||||||
self._builtin_value = builtin_value
|
self._builtin_value = builtin_value
|
||||||
"""
|
"""
|
||||||
@@ -311,7 +306,7 @@ class SpecialMethodFilter(DictFilter):
|
|||||||
|
|
||||||
class _OverwriteMeta(type):
|
class _OverwriteMeta(type):
|
||||||
def __init__(cls, name, bases, dct):
|
def __init__(cls, name, bases, dct):
|
||||||
super(_OverwriteMeta, cls).__init__(name, bases, dct)
|
super().__init__(name, bases, dct)
|
||||||
|
|
||||||
base_dct = {}
|
base_dct = {}
|
||||||
for base_cls in reversed(cls.__bases__):
|
for base_cls in reversed(cls.__bases__):
|
||||||
@@ -328,28 +323,26 @@ class _OverwriteMeta(type):
|
|||||||
cls.overwritten_methods = base_dct
|
cls.overwritten_methods = base_dct
|
||||||
|
|
||||||
|
|
||||||
class _AttributeOverwriteMixin(object):
|
class _AttributeOverwriteMixin:
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
|
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
|
||||||
|
yield from self._wrapped_value.get_filters(*args, **kwargs)
|
||||||
for filter in self._wrapped_value.get_filters():
|
|
||||||
yield filter
|
|
||||||
|
|
||||||
|
|
||||||
class LazyAttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin,
|
class LazyAttributeOverwrite(_AttributeOverwriteMixin, LazyValueWrapper,
|
||||||
LazyValueWrapper)):
|
metaclass=_OverwriteMeta):
|
||||||
def __init__(self, inference_state):
|
def __init__(self, inference_state):
|
||||||
self.inference_state = inference_state
|
self.inference_state = inference_state
|
||||||
|
|
||||||
|
|
||||||
class AttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin,
|
class AttributeOverwrite(_AttributeOverwriteMixin, ValueWrapper,
|
||||||
ValueWrapper)):
|
metaclass=_OverwriteMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def publish_method(method_name, python_version_match=None):
|
def publish_method(method_name):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
dct = func.__dict__.setdefault('registered_overwritten_methods', {})
|
dct = func.__dict__.setdefault('registered_overwritten_methods', {})
|
||||||
dct[method_name] = func, python_version_match
|
dct[method_name] = func
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
|
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
|
||||||
from jedi.inference.recursion import execution_allowed
|
from jedi.inference.recursion import execution_allowed
|
||||||
from jedi.inference.helpers import is_big_annoying_library
|
from jedi.inference.helpers import is_big_annoying_library
|
||||||
|
|
||||||
|
|
||||||
class Status(object):
|
class Status:
|
||||||
lookup_table = {}
|
lookup_table: Dict[Optional[bool], 'Status'] = {}
|
||||||
|
|
||||||
def __init__(self, value, name):
|
def __init__(self, value: Optional[bool], name: str) -> None:
|
||||||
self._value = value
|
self._value = value
|
||||||
self._name = name
|
self._name = name
|
||||||
Status.lookup_table[value] = self
|
Status.lookup_table[value] = self
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
"""
|
||||||
|
It is unfortunately not well documented how stubs and annotations work in Jedi.
|
||||||
|
If somebody needs an introduction, please let me know.
|
||||||
|
"""
|
||||||
@@ -6,15 +6,14 @@ as annotations in future python versions.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from inspect import Parameter
|
||||||
|
|
||||||
from parso import ParserSyntaxError, parse
|
from parso import ParserSyntaxError, parse
|
||||||
|
|
||||||
from jedi._compatibility import force_unicode, Parameter
|
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
from jedi.inference.base_value import ValueSet, NO_VALUES
|
||||||
from jedi.inference.gradual.base import DefineGenericBase, GenericClass
|
from jedi.inference.gradual.base import DefineGenericBaseClass, GenericClass
|
||||||
from jedi.inference.gradual.generics import TupleGenericManager
|
from jedi.inference.gradual.generics import TupleGenericManager
|
||||||
from jedi.inference.gradual.typing import TypingClassValueWithIndex
|
|
||||||
from jedi.inference.gradual.type_var import TypeVar
|
from jedi.inference.gradual.type_var import TypeVar
|
||||||
from jedi.inference.helpers import is_string
|
from jedi.inference.helpers import is_string
|
||||||
from jedi.inference.compiled import builtin_from_name
|
from jedi.inference.compiled import builtin_from_name
|
||||||
@@ -54,8 +53,10 @@ def _infer_annotation_string(context, string, index=None):
|
|||||||
value_set = context.infer_node(node)
|
value_set = context.infer_node(node)
|
||||||
if index is not None:
|
if index is not None:
|
||||||
value_set = value_set.filter(
|
value_set = value_set.filter(
|
||||||
lambda value: value.array_type == u'tuple' # noqa
|
lambda value: (
|
||||||
and len(list(value.py__iter__())) >= index
|
value.array_type == 'tuple'
|
||||||
|
and len(list(value.py__iter__())) >= index
|
||||||
|
)
|
||||||
).py__simple_getitem__(index)
|
).py__simple_getitem__(index)
|
||||||
return value_set
|
return value_set
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ def _infer_annotation_string(context, string, index=None):
|
|||||||
def _get_forward_reference_node(context, string):
|
def _get_forward_reference_node(context, string):
|
||||||
try:
|
try:
|
||||||
new_node = context.inference_state.grammar.parse(
|
new_node = context.inference_state.grammar.parse(
|
||||||
force_unicode(string),
|
string,
|
||||||
start_symbol='eval_input',
|
start_symbol='eval_input',
|
||||||
error_recovery=False
|
error_recovery=False
|
||||||
)
|
)
|
||||||
@@ -111,7 +112,7 @@ def _split_comment_param_declaration(decl_text):
|
|||||||
@inference_state_method_cache()
|
@inference_state_method_cache()
|
||||||
def infer_param(function_value, param, ignore_stars=False):
|
def infer_param(function_value, param, ignore_stars=False):
|
||||||
values = _infer_param(function_value, param)
|
values = _infer_param(function_value, param)
|
||||||
if ignore_stars:
|
if ignore_stars or not values:
|
||||||
return values
|
return values
|
||||||
inference_state = function_value.inference_state
|
inference_state = function_value.inference_state
|
||||||
if param.star_count == 1:
|
if param.star_count == 1:
|
||||||
@@ -119,7 +120,7 @@ def infer_param(function_value, param, ignore_stars=False):
|
|||||||
return ValueSet([GenericClass(
|
return ValueSet([GenericClass(
|
||||||
tuple_,
|
tuple_,
|
||||||
TupleGenericManager((values,)),
|
TupleGenericManager((values,)),
|
||||||
) for c in values])
|
)])
|
||||||
elif param.star_count == 2:
|
elif param.star_count == 2:
|
||||||
dct = builtin_from_name(inference_state, 'dict')
|
dct = builtin_from_name(inference_state, 'dict')
|
||||||
generics = (
|
generics = (
|
||||||
@@ -129,8 +130,7 @@ def infer_param(function_value, param, ignore_stars=False):
|
|||||||
return ValueSet([GenericClass(
|
return ValueSet([GenericClass(
|
||||||
dct,
|
dct,
|
||||||
TupleGenericManager(generics),
|
TupleGenericManager(generics),
|
||||||
) for c in values])
|
)])
|
||||||
pass
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
@@ -140,8 +140,7 @@ def _infer_param(function_value, param):
|
|||||||
"""
|
"""
|
||||||
annotation = param.annotation
|
annotation = param.annotation
|
||||||
if annotation is None:
|
if annotation is None:
|
||||||
# If no Python 3-style annotation, look for a Python 2-style comment
|
# If no Python 3-style annotation, look for a comment annotation.
|
||||||
# annotation.
|
|
||||||
# Identify parameters to function in the same sequence as they would
|
# Identify parameters to function in the same sequence as they would
|
||||||
# appear in a type comment.
|
# appear in a type comment.
|
||||||
all_params = [child for child in param.parent.children
|
all_params = [child for child in param.parent.children
|
||||||
@@ -206,7 +205,8 @@ def infer_return_types(function, arguments):
|
|||||||
all_annotations = py__annotations__(function.tree_node)
|
all_annotations = py__annotations__(function.tree_node)
|
||||||
annotation = all_annotations.get("return", None)
|
annotation = all_annotations.get("return", None)
|
||||||
if annotation is None:
|
if annotation is None:
|
||||||
# If there is no Python 3-type annotation, look for a Python 2-type annotation
|
# If there is no Python 3-type annotation, look for an annotation
|
||||||
|
# comment.
|
||||||
node = function.tree_node
|
node = function.tree_node
|
||||||
comment = parser_utils.get_following_comment_same_line(node)
|
comment = parser_utils.get_following_comment_same_line(node)
|
||||||
if comment is None:
|
if comment is None:
|
||||||
@@ -220,8 +220,6 @@ def infer_return_types(function, arguments):
|
|||||||
function.get_default_param_context(),
|
function.get_default_param_context(),
|
||||||
match.group(1).strip()
|
match.group(1).strip()
|
||||||
).execute_annotation()
|
).execute_annotation()
|
||||||
if annotation is None:
|
|
||||||
return NO_VALUES
|
|
||||||
|
|
||||||
context = function.get_default_param_context()
|
context = function.get_default_param_context()
|
||||||
unknown_type_vars = find_unknown_type_vars(context, annotation)
|
unknown_type_vars = find_unknown_type_vars(context, annotation)
|
||||||
@@ -233,7 +231,7 @@ def infer_return_types(function, arguments):
|
|||||||
|
|
||||||
return ValueSet.from_sets(
|
return ValueSet.from_sets(
|
||||||
ann.define_generics(type_var_dict)
|
ann.define_generics(type_var_dict)
|
||||||
if isinstance(ann, (DefineGenericBase, TypeVar)) else ValueSet({ann})
|
if isinstance(ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann})
|
||||||
for ann in annotation_values
|
for ann in annotation_values
|
||||||
).execute_annotation()
|
).execute_annotation()
|
||||||
|
|
||||||
@@ -269,29 +267,29 @@ def infer_type_vars_for_execution(function, arguments, annotation_dict):
|
|||||||
elif kind is Parameter.VAR_KEYWORD:
|
elif kind is Parameter.VAR_KEYWORD:
|
||||||
# TODO _dict_values is not public.
|
# TODO _dict_values is not public.
|
||||||
actual_value_set = actual_value_set.try_merge('_dict_values')
|
actual_value_set = actual_value_set.try_merge('_dict_values')
|
||||||
for ann in annotation_value_set:
|
merge_type_var_dicts(
|
||||||
_merge_type_var_dicts(
|
annotation_variable_results,
|
||||||
annotation_variable_results,
|
annotation_value_set.infer_type_vars(actual_value_set),
|
||||||
_infer_type_vars(ann, actual_value_set),
|
)
|
||||||
)
|
|
||||||
return annotation_variable_results
|
return annotation_variable_results
|
||||||
|
|
||||||
|
|
||||||
def infer_return_for_callable(arguments, param_values, result_values):
|
def infer_return_for_callable(arguments, param_values, result_values):
|
||||||
result = NO_VALUES
|
all_type_vars = {}
|
||||||
for pv in param_values:
|
for pv in param_values:
|
||||||
if pv.array_type == 'list':
|
if pv.array_type == 'list':
|
||||||
type_var_dict = infer_type_vars_for_callable(arguments, pv.py__iter__())
|
type_var_dict = _infer_type_vars_for_callable(arguments, pv.py__iter__())
|
||||||
|
all_type_vars.update(type_var_dict)
|
||||||
|
|
||||||
result |= ValueSet.from_sets(
|
return ValueSet.from_sets(
|
||||||
v.define_generics(type_var_dict)
|
v.define_generics(all_type_vars)
|
||||||
if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v})
|
if isinstance(v, (DefineGenericBaseClass, TypeVar))
|
||||||
for v in result_values
|
else ValueSet({v})
|
||||||
).execute_annotation()
|
for v in result_values
|
||||||
return result
|
).execute_annotation()
|
||||||
|
|
||||||
|
|
||||||
def infer_type_vars_for_callable(arguments, lazy_params):
|
def _infer_type_vars_for_callable(arguments, lazy_params):
|
||||||
"""
|
"""
|
||||||
Infers type vars for the Calllable class:
|
Infers type vars for the Calllable class:
|
||||||
|
|
||||||
@@ -302,15 +300,14 @@ def infer_type_vars_for_callable(arguments, lazy_params):
|
|||||||
callable_param_values = lazy_callable_param.infer()
|
callable_param_values = lazy_callable_param.infer()
|
||||||
# Infer unknown type var
|
# Infer unknown type var
|
||||||
actual_value_set = lazy_value.infer()
|
actual_value_set = lazy_value.infer()
|
||||||
for v in callable_param_values:
|
merge_type_var_dicts(
|
||||||
_merge_type_var_dicts(
|
annotation_variable_results,
|
||||||
annotation_variable_results,
|
callable_param_values.infer_type_vars(actual_value_set),
|
||||||
_infer_type_vars(v, actual_value_set),
|
)
|
||||||
)
|
|
||||||
return annotation_variable_results
|
return annotation_variable_results
|
||||||
|
|
||||||
|
|
||||||
def _merge_type_var_dicts(base_dict, new_dict):
|
def merge_type_var_dicts(base_dict, new_dict):
|
||||||
for type_var_name, values in new_dict.items():
|
for type_var_name, values in new_dict.items():
|
||||||
if values:
|
if values:
|
||||||
try:
|
try:
|
||||||
@@ -319,88 +316,55 @@ def _merge_type_var_dicts(base_dict, new_dict):
|
|||||||
base_dict[type_var_name] = values
|
base_dict[type_var_name] = values
|
||||||
|
|
||||||
|
|
||||||
def _infer_type_vars(annotation_value, value_set, is_class_value=False):
|
def merge_pairwise_generics(annotation_value, annotated_argument_class):
|
||||||
"""
|
"""
|
||||||
This function tries to find information about undefined type vars and
|
Match up the generic parameters from the given argument class to the
|
||||||
returns a dict from type var name to value set.
|
target annotation.
|
||||||
|
|
||||||
This is for example important to understand what `iter([1])` returns.
|
This walks the generic parameters immediately within the annotation and
|
||||||
According to typeshed, `iter` returns an `Iterator[_T]`:
|
argument's type, in order to determine the concrete values of the
|
||||||
|
annotation's parameters for the current case.
|
||||||
|
|
||||||
def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
|
For example, given the following code:
|
||||||
|
|
||||||
This functions would generate `int` for `_T` in this case, because it
|
def values(mapping: Mapping[K, V]) -> List[V]: ...
|
||||||
unpacks the `Iterable`.
|
|
||||||
|
for val in values({1: 'a'}):
|
||||||
|
val
|
||||||
|
|
||||||
|
Then this function should be given representations of `Mapping[K, V]`
|
||||||
|
and `Mapping[int, str]`, so that it can determine that `K` is `int and
|
||||||
|
`V` is `str`.
|
||||||
|
|
||||||
|
Note that it is responsibility of the caller to traverse the MRO of the
|
||||||
|
argument type as needed in order to find the type matching the
|
||||||
|
annotation (in this case finding `Mapping[int, str]` as a parent of
|
||||||
|
`Dict[int, str]`).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
|
||||||
|
`annotation_value`: represents the annotation to infer the concrete
|
||||||
|
parameter types of.
|
||||||
|
|
||||||
|
`annotated_argument_class`: represents the annotated class of the
|
||||||
|
argument being passed to the object annotated by `annotation_value`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type_var_dict = {}
|
type_var_dict = {}
|
||||||
if isinstance(annotation_value, TypeVar):
|
|
||||||
if not is_class_value:
|
|
||||||
return {annotation_value.py__name__(): value_set.py__class__()}
|
|
||||||
return {annotation_value.py__name__(): value_set}
|
|
||||||
elif isinstance(annotation_value, TypingClassValueWithIndex):
|
|
||||||
name = annotation_value.py__name__()
|
|
||||||
if name == 'Type':
|
|
||||||
given = annotation_value.get_generics()
|
|
||||||
if given:
|
|
||||||
for nested_annotation_value in given[0]:
|
|
||||||
_merge_type_var_dicts(
|
|
||||||
type_var_dict,
|
|
||||||
_infer_type_vars(
|
|
||||||
nested_annotation_value,
|
|
||||||
value_set,
|
|
||||||
is_class_value=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif name == 'Callable':
|
|
||||||
given = annotation_value.get_generics()
|
|
||||||
if len(given) == 2:
|
|
||||||
for nested_annotation_value in given[1]:
|
|
||||||
_merge_type_var_dicts(
|
|
||||||
type_var_dict,
|
|
||||||
_infer_type_vars(
|
|
||||||
nested_annotation_value,
|
|
||||||
value_set.execute_annotation(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif isinstance(annotation_value, GenericClass):
|
|
||||||
name = annotation_value.py__name__()
|
|
||||||
if name == 'Iterable':
|
|
||||||
given = annotation_value.get_generics()
|
|
||||||
if given:
|
|
||||||
for nested_annotation_value in given[0]:
|
|
||||||
_merge_type_var_dicts(
|
|
||||||
type_var_dict,
|
|
||||||
_infer_type_vars(
|
|
||||||
nested_annotation_value,
|
|
||||||
value_set.merge_types_of_iterate()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif name == 'Mapping':
|
|
||||||
given = annotation_value.get_generics()
|
|
||||||
if len(given) == 2:
|
|
||||||
for value in value_set:
|
|
||||||
try:
|
|
||||||
method = value.get_mapping_item_values
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
key_values, value_values = method()
|
|
||||||
|
|
||||||
for nested_annotation_value in given[0]:
|
if not isinstance(annotated_argument_class, DefineGenericBaseClass):
|
||||||
_merge_type_var_dicts(
|
return type_var_dict
|
||||||
type_var_dict,
|
|
||||||
_infer_type_vars(
|
annotation_generics = annotation_value.get_generics()
|
||||||
nested_annotation_value,
|
actual_generics = annotated_argument_class.get_generics()
|
||||||
key_values,
|
|
||||||
)
|
for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics):
|
||||||
)
|
merge_type_var_dicts(
|
||||||
for nested_annotation_value in given[1]:
|
type_var_dict,
|
||||||
_merge_type_var_dicts(
|
annotation_generics_set.infer_type_vars(actual_generic_set.execute_annotation()),
|
||||||
type_var_dict,
|
)
|
||||||
_infer_type_vars(
|
|
||||||
nested_annotation_value,
|
|
||||||
value_values,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return type_var_dict
|
return type_var_dict
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+142
-29
@@ -23,10 +23,9 @@ class _BoundTypeVarName(AbstractNameDefinition):
|
|||||||
def iter_():
|
def iter_():
|
||||||
for value in self._value_set:
|
for value in self._value_set:
|
||||||
# Replace any with the constraints if they are there.
|
# Replace any with the constraints if they are there.
|
||||||
from jedi.inference.gradual.typing import Any
|
from jedi.inference.gradual.typing import AnyClass
|
||||||
if isinstance(value, Any):
|
if isinstance(value, AnyClass):
|
||||||
for constraint in self._type_var.constraints:
|
yield from self._type_var.constraints
|
||||||
yield constraint
|
|
||||||
else:
|
else:
|
||||||
yield value
|
yield value
|
||||||
return ValueSet(iter_())
|
return ValueSet(iter_())
|
||||||
@@ -38,7 +37,7 @@ class _BoundTypeVarName(AbstractNameDefinition):
|
|||||||
return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set)
|
return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set)
|
||||||
|
|
||||||
|
|
||||||
class _TypeVarFilter(object):
|
class _TypeVarFilter:
|
||||||
"""
|
"""
|
||||||
A filter for all given variables in a class.
|
A filter for all given variables in a class.
|
||||||
|
|
||||||
@@ -70,18 +69,17 @@ class _TypeVarFilter(object):
|
|||||||
|
|
||||||
class _AnnotatedClassContext(ClassContext):
|
class _AnnotatedClassContext(ClassContext):
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
filters = super(_AnnotatedClassContext, self).get_filters(
|
filters = super().get_filters(
|
||||||
*args, **kwargs
|
*args, **kwargs
|
||||||
)
|
)
|
||||||
for f in filters:
|
yield from filters
|
||||||
yield f
|
|
||||||
|
|
||||||
# The type vars can only be looked up if it's a global search and
|
# The type vars can only be looked up if it's a global search and
|
||||||
# not a direct lookup on the class.
|
# not a direct lookup on the class.
|
||||||
yield self._value.get_type_var_filter()
|
yield self._value.get_type_var_filter()
|
||||||
|
|
||||||
|
|
||||||
class DefineGenericBase(LazyValueWrapper):
|
class DefineGenericBaseClass(LazyValueWrapper):
|
||||||
def __init__(self, generics_manager):
|
def __init__(self, generics_manager):
|
||||||
self._generics_manager = generics_manager
|
self._generics_manager = generics_manager
|
||||||
|
|
||||||
@@ -99,7 +97,7 @@ class DefineGenericBase(LazyValueWrapper):
|
|||||||
for generic_set in self.get_generics():
|
for generic_set in self.get_generics():
|
||||||
values = NO_VALUES
|
values = NO_VALUES
|
||||||
for generic in generic_set:
|
for generic in generic_set:
|
||||||
if isinstance(generic, (GenericClass, TypeVar)):
|
if isinstance(generic, (DefineGenericBaseClass, TypeVar)):
|
||||||
result = generic.define_generics(type_var_dict)
|
result = generic.define_generics(type_var_dict)
|
||||||
values |= result
|
values |= result
|
||||||
if result != ValueSet({generic}):
|
if result != ValueSet({generic}):
|
||||||
@@ -119,7 +117,7 @@ class DefineGenericBase(LazyValueWrapper):
|
|||||||
)])
|
)])
|
||||||
|
|
||||||
def is_same_class(self, other):
|
def is_same_class(self, other):
|
||||||
if not isinstance(other, DefineGenericBase):
|
if not isinstance(other, DefineGenericBaseClass):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.tree_node != other.tree_node:
|
if self.tree_node != other.tree_node:
|
||||||
@@ -138,11 +136,19 @@ class DefineGenericBase(LazyValueWrapper):
|
|||||||
any(
|
any(
|
||||||
# TODO why is this ordering the correct one?
|
# TODO why is this ordering the correct one?
|
||||||
cls2.is_same_class(cls1)
|
cls2.is_same_class(cls1)
|
||||||
for cls1 in class_set1
|
# TODO I'm still not sure gather_annotation_classes is a good
|
||||||
for cls2 in class_set2
|
# idea. They are essentially here to avoid comparing Tuple <=>
|
||||||
|
# tuple and instead compare tuple <=> tuple, but at the moment
|
||||||
|
# the whole `is_same_class` and `is_sub_class` matching is just
|
||||||
|
# not in the best shape.
|
||||||
|
for cls1 in class_set1.gather_annotation_classes()
|
||||||
|
for cls2 in class_set2.gather_annotation_classes()
|
||||||
) for class_set1, class_set2 in zip(given_params1, given_params2)
|
) for class_set1, class_set2 in zip(given_params1, given_params2)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_signatures(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s%s>' % (
|
return '<%s: %s%s>' % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
@@ -151,7 +157,7 @@ class DefineGenericBase(LazyValueWrapper):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GenericClass(ClassMixin, DefineGenericBase):
|
class GenericClass(DefineGenericBaseClass, ClassMixin):
|
||||||
"""
|
"""
|
||||||
A class that is defined with generics, might be something simple like:
|
A class that is defined with generics, might be something simple like:
|
||||||
|
|
||||||
@@ -159,17 +165,29 @@ class GenericClass(ClassMixin, DefineGenericBase):
|
|||||||
my_foo_int_cls = Foo[int]
|
my_foo_int_cls = Foo[int]
|
||||||
"""
|
"""
|
||||||
def __init__(self, class_value, generics_manager):
|
def __init__(self, class_value, generics_manager):
|
||||||
super(GenericClass, self).__init__(generics_manager)
|
super().__init__(generics_manager)
|
||||||
self._class_value = class_value
|
self._class_value = class_value
|
||||||
|
|
||||||
def _get_wrapped_value(self):
|
def _get_wrapped_value(self):
|
||||||
return self._class_value
|
return self._class_value
|
||||||
|
|
||||||
|
def get_type_hint(self, add_class_info=True):
|
||||||
|
n = self.py__name__()
|
||||||
|
# Not sure if this is the best way to do this, but all of these types
|
||||||
|
# are a bit special in that they have type aliases and other ways to
|
||||||
|
# become lower case. It's probably better to make them upper case,
|
||||||
|
# because that's what you can use in annotations.
|
||||||
|
n = dict(list="List", dict="Dict", set="Set", tuple="Tuple").get(n, n)
|
||||||
|
s = n + self._generics_manager.get_type_hint()
|
||||||
|
if add_class_info:
|
||||||
|
return 'Type[%s]' % s
|
||||||
|
return s
|
||||||
|
|
||||||
def get_type_var_filter(self):
|
def get_type_var_filter(self):
|
||||||
return _TypeVarFilter(self.get_generics(), self.list_type_vars())
|
return _TypeVarFilter(self.get_generics(), self.list_type_vars())
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
instance, = super(GenericClass, self).py__call__(arguments)
|
instance, = super().py__call__(arguments)
|
||||||
return ValueSet([_GenericInstanceWrapper(instance)])
|
return ValueSet([_GenericInstanceWrapper(instance)])
|
||||||
|
|
||||||
def _as_context(self):
|
def _as_context(self):
|
||||||
@@ -178,21 +196,64 @@ class GenericClass(ClassMixin, DefineGenericBase):
|
|||||||
@to_list
|
@to_list
|
||||||
def py__bases__(self):
|
def py__bases__(self):
|
||||||
for base in self._wrapped_value.py__bases__():
|
for base in self._wrapped_value.py__bases__():
|
||||||
yield _LazyGenericBaseClass(self, base)
|
yield _LazyGenericBaseClass(self, base, self._generics_manager)
|
||||||
|
|
||||||
def _create_instance_with_generics(self, generics_manager):
|
def _create_instance_with_generics(self, generics_manager):
|
||||||
return GenericClass(self._class_value, generics_manager)
|
return GenericClass(self._class_value, generics_manager)
|
||||||
|
|
||||||
def is_sub_class_of(self, class_value):
|
def is_sub_class_of(self, class_value):
|
||||||
if super(GenericClass, self).is_sub_class_of(class_value):
|
if super().is_sub_class_of(class_value):
|
||||||
return True
|
return True
|
||||||
return self._class_value.is_sub_class_of(class_value)
|
return self._class_value.is_sub_class_of(class_value)
|
||||||
|
|
||||||
|
def with_generics(self, generics_tuple):
|
||||||
|
return self._class_value.with_generics(generics_tuple)
|
||||||
|
|
||||||
class _LazyGenericBaseClass(object):
|
def infer_type_vars(self, value_set):
|
||||||
def __init__(self, class_value, lazy_base_class):
|
# Circular
|
||||||
|
from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts
|
||||||
|
|
||||||
|
annotation_name = self.py__name__()
|
||||||
|
type_var_dict = {}
|
||||||
|
if annotation_name == 'Iterable':
|
||||||
|
annotation_generics = self.get_generics()
|
||||||
|
if annotation_generics:
|
||||||
|
return annotation_generics[0].infer_type_vars(
|
||||||
|
value_set.merge_types_of_iterate(),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Note: we need to handle the MRO _in order_, so we need to extract
|
||||||
|
# the elements from the set first, then handle them, even if we put
|
||||||
|
# them back in a set afterwards.
|
||||||
|
for py_class in value_set:
|
||||||
|
if py_class.is_instance() and not py_class.is_compiled():
|
||||||
|
py_class = py_class.get_annotated_class_object()
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if py_class.api_type != 'class':
|
||||||
|
# Functions & modules don't have an MRO and we're not
|
||||||
|
# expecting a Callable (those are handled separately within
|
||||||
|
# TypingClassValueWithIndex).
|
||||||
|
continue
|
||||||
|
|
||||||
|
for parent_class in py_class.py__mro__():
|
||||||
|
class_name = parent_class.py__name__()
|
||||||
|
if annotation_name == class_name:
|
||||||
|
merge_type_var_dicts(
|
||||||
|
type_var_dict,
|
||||||
|
merge_pairwise_generics(self, parent_class),
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
return type_var_dict
|
||||||
|
|
||||||
|
|
||||||
|
class _LazyGenericBaseClass:
|
||||||
|
def __init__(self, class_value, lazy_base_class, generics_manager):
|
||||||
self._class_value = class_value
|
self._class_value = class_value
|
||||||
self._lazy_base_class = lazy_base_class
|
self._lazy_base_class = lazy_base_class
|
||||||
|
self._generics_manager = generics_manager
|
||||||
|
|
||||||
@iterator_to_value_set
|
@iterator_to_value_set
|
||||||
def infer(self):
|
def infer(self):
|
||||||
@@ -205,7 +266,17 @@ class _LazyGenericBaseClass(object):
|
|||||||
TupleGenericManager(tuple(self._remap_type_vars(base))),
|
TupleGenericManager(tuple(self._remap_type_vars(base))),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
yield base
|
if base.is_class_mixin():
|
||||||
|
# This case basically allows classes like `class Foo(List)`
|
||||||
|
# to be used like `Foo[int]`. The generics are not
|
||||||
|
# necessary and can be used later.
|
||||||
|
yield GenericClass.create_cached(
|
||||||
|
base.inference_state,
|
||||||
|
base,
|
||||||
|
self._generics_manager,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
yield base
|
||||||
|
|
||||||
def _remap_type_vars(self, base):
|
def _remap_type_vars(self, base):
|
||||||
from jedi.inference.gradual.type_var import TypeVar
|
from jedi.inference.gradual.type_var import TypeVar
|
||||||
@@ -225,6 +296,9 @@ class _LazyGenericBaseClass(object):
|
|||||||
new |= ValueSet([type_var])
|
new |= ValueSet([type_var])
|
||||||
yield new
|
yield new
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self._lazy_base_class)
|
||||||
|
|
||||||
|
|
||||||
class _GenericInstanceWrapper(ValueWrapper):
|
class _GenericInstanceWrapper(ValueWrapper):
|
||||||
def py__stop_iteration_returns(self):
|
def py__stop_iteration_returns(self):
|
||||||
@@ -236,9 +310,12 @@ class _GenericInstanceWrapper(ValueWrapper):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
elif cls.py__name__() == 'Iterator':
|
elif cls.py__name__() == 'Iterator':
|
||||||
return ValueSet([builtin_from_name(self.inference_state, u'None')])
|
return ValueSet([builtin_from_name(self.inference_state, 'None')])
|
||||||
return self._wrapped_value.py__stop_iteration_returns()
|
return self._wrapped_value.py__stop_iteration_returns()
|
||||||
|
|
||||||
|
def get_type_hint(self, add_class_info=True):
|
||||||
|
return self._wrapped_value.class_value.get_type_hint(add_class_info=False)
|
||||||
|
|
||||||
|
|
||||||
class _PseudoTreeNameClass(Value):
|
class _PseudoTreeNameClass(Value):
|
||||||
"""
|
"""
|
||||||
@@ -250,8 +327,10 @@ class _PseudoTreeNameClass(Value):
|
|||||||
this class. Essentially this class makes it possible to goto that `Tuple`
|
this class. Essentially this class makes it possible to goto that `Tuple`
|
||||||
name, without affecting anything else negatively.
|
name, without affecting anything else negatively.
|
||||||
"""
|
"""
|
||||||
|
api_type = 'class'
|
||||||
|
|
||||||
def __init__(self, parent_context, tree_name):
|
def __init__(self, parent_context, tree_name):
|
||||||
super(_PseudoTreeNameClass, self).__init__(
|
super().__init__(
|
||||||
parent_context.inference_state,
|
parent_context.inference_state,
|
||||||
parent_context
|
parent_context
|
||||||
)
|
)
|
||||||
@@ -276,15 +355,17 @@ class _PseudoTreeNameClass(Value):
|
|||||||
yield EmptyFilter()
|
yield EmptyFilter()
|
||||||
|
|
||||||
def py__class__(self):
|
def py__class__(self):
|
||||||
# TODO this is obviously not correct, but at least gives us a class if
|
# This might not be 100% correct, but it is good enough. The details of
|
||||||
# we have none. Some of these objects don't really have a base class in
|
# the typing library are not really an issue for Jedi.
|
||||||
# typeshed.
|
return builtin_from_name(self.inference_state, 'type')
|
||||||
return builtin_from_name(self.inference_state, u'object')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return ValueName(self, self._tree_name)
|
return ValueName(self, self._tree_name)
|
||||||
|
|
||||||
|
def get_qualified_names(self):
|
||||||
|
return (self._tree_name.value,)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
|
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
|
||||||
|
|
||||||
@@ -302,13 +383,16 @@ class BaseTypingValue(LazyValueWrapper):
|
|||||||
def _get_wrapped_value(self):
|
def _get_wrapped_value(self):
|
||||||
return _PseudoTreeNameClass(self.parent_context, self._tree_name)
|
return _PseudoTreeNameClass(self.parent_context, self._tree_name)
|
||||||
|
|
||||||
|
def get_signatures(self):
|
||||||
|
return self._wrapped_value.get_signatures()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
|
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
|
||||||
|
|
||||||
|
|
||||||
class BaseTypingValueWithGenerics(DefineGenericBase):
|
class BaseTypingClassWithGenerics(DefineGenericBaseClass):
|
||||||
def __init__(self, parent_context, tree_name, generics_manager):
|
def __init__(self, parent_context, tree_name, generics_manager):
|
||||||
super(BaseTypingValueWithGenerics, self).__init__(generics_manager)
|
super().__init__(generics_manager)
|
||||||
self.inference_state = parent_context.inference_state
|
self.inference_state = parent_context.inference_state
|
||||||
self.parent_context = parent_context
|
self.parent_context = parent_context
|
||||||
self._tree_name = tree_name
|
self._tree_name = tree_name
|
||||||
@@ -319,3 +403,32 @@ class BaseTypingValueWithGenerics(DefineGenericBase):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '%s(%s%s)' % (self.__class__.__name__, self._tree_name.value,
|
return '%s(%s%s)' % (self.__class__.__name__, self._tree_name.value,
|
||||||
self._generics_manager)
|
self._generics_manager)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTypingInstance(LazyValueWrapper):
|
||||||
|
def __init__(self, parent_context, class_value, tree_name, generics_manager):
|
||||||
|
self.inference_state = class_value.inference_state
|
||||||
|
self.parent_context = parent_context
|
||||||
|
self._class_value = class_value
|
||||||
|
self._tree_name = tree_name
|
||||||
|
self._generics_manager = generics_manager
|
||||||
|
|
||||||
|
def py__class__(self):
|
||||||
|
return self._class_value
|
||||||
|
|
||||||
|
def get_annotated_class_object(self):
|
||||||
|
return self._class_value
|
||||||
|
|
||||||
|
def get_qualified_names(self):
|
||||||
|
return (self.py__name__(),)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return ValueName(self, self._tree_name)
|
||||||
|
|
||||||
|
def _get_wrapped_value(self):
|
||||||
|
object_, = builtin_from_name(self.inference_state, 'object').execute_annotation()
|
||||||
|
return object_
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self._generics_manager)
|
||||||
@@ -3,6 +3,8 @@ from jedi.inference.base_value import ValueSet, \
|
|||||||
NO_VALUES
|
NO_VALUES
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
from jedi.inference.gradual.stub_value import StubModuleValue
|
from jedi.inference.gradual.stub_value import StubModuleValue
|
||||||
|
from jedi.inference.gradual.typeshed import try_to_load_stub_cached
|
||||||
|
from jedi.inference.value.decorator import Decoratee
|
||||||
|
|
||||||
|
|
||||||
def _stub_to_python_value_set(stub_value, ignore_compiled=False):
|
def _stub_to_python_value_set(stub_value, ignore_compiled=False):
|
||||||
@@ -10,8 +12,13 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False):
|
|||||||
if not stub_module_context.is_stub():
|
if not stub_module_context.is_stub():
|
||||||
return ValueSet([stub_value])
|
return ValueSet([stub_value])
|
||||||
|
|
||||||
|
decorates = None
|
||||||
|
if isinstance(stub_value, Decoratee):
|
||||||
|
decorates = stub_value._original_value
|
||||||
|
|
||||||
was_instance = stub_value.is_instance()
|
was_instance = stub_value.is_instance()
|
||||||
if was_instance:
|
if was_instance:
|
||||||
|
arguments = getattr(stub_value, '_arguments', None)
|
||||||
stub_value = stub_value.py__class__()
|
stub_value = stub_value.py__class__()
|
||||||
|
|
||||||
qualified_names = stub_value.get_qualified_names()
|
qualified_names = stub_value.get_qualified_names()
|
||||||
@@ -24,11 +31,12 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False):
|
|||||||
method_name = qualified_names[-1]
|
method_name = qualified_names[-1]
|
||||||
qualified_names = qualified_names[:-1]
|
qualified_names = qualified_names[:-1]
|
||||||
was_instance = True
|
was_instance = True
|
||||||
|
arguments = None
|
||||||
|
|
||||||
values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled)
|
values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled)
|
||||||
if was_instance:
|
if was_instance:
|
||||||
values = ValueSet.from_sets(
|
values = ValueSet.from_sets(
|
||||||
c.execute_with_values()
|
c.execute_with_values() if arguments is None else c.execute(arguments)
|
||||||
for c in values
|
for c in values
|
||||||
if c.is_class()
|
if c.is_class()
|
||||||
)
|
)
|
||||||
@@ -36,6 +44,8 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False):
|
|||||||
# Now that the instance has been properly created, we can simply get
|
# Now that the instance has been properly created, we can simply get
|
||||||
# the method.
|
# the method.
|
||||||
values = values.py__getattribute__(method_name)
|
values = values.py__getattribute__(method_name)
|
||||||
|
if decorates is not None:
|
||||||
|
values = ValueSet(Decoratee(v, decorates) for v in values)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
@@ -73,7 +83,13 @@ def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
|
|||||||
converted_names = converted.goto(name.get_public_name())
|
converted_names = converted.goto(name.get_public_name())
|
||||||
if converted_names:
|
if converted_names:
|
||||||
for n in converted_names:
|
for n in converted_names:
|
||||||
yield n
|
if n.get_root_context().is_stub():
|
||||||
|
# If it's a stub again, it means we're going in
|
||||||
|
# a circle. Probably some imports make it a
|
||||||
|
# stub again.
|
||||||
|
yield name
|
||||||
|
else:
|
||||||
|
yield n
|
||||||
continue
|
continue
|
||||||
yield name
|
yield name
|
||||||
|
|
||||||
@@ -81,8 +97,7 @@ def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
|
|||||||
def _load_stub_module(module):
|
def _load_stub_module(module):
|
||||||
if module.is_stub():
|
if module.is_stub():
|
||||||
return module
|
return module
|
||||||
from jedi.inference.gradual.typeshed import _try_to_load_stub_cached
|
return try_to_load_stub_cached(
|
||||||
return _try_to_load_stub_cached(
|
|
||||||
module.inference_state,
|
module.inference_state,
|
||||||
import_names=module.string_names,
|
import_names=module.string_names,
|
||||||
python_value_set=ValueSet([module]),
|
python_value_set=ValueSet([module]),
|
||||||
@@ -120,8 +135,7 @@ def _python_to_stub_names(names, fallback_to_python=False):
|
|||||||
if converted:
|
if converted:
|
||||||
converted_names = converted.goto(name.get_public_name())
|
converted_names = converted.goto(name.get_public_name())
|
||||||
if converted_names:
|
if converted_names:
|
||||||
for n in converted_names:
|
yield from converted_names
|
||||||
yield n
|
|
||||||
continue
|
continue
|
||||||
if fallback_to_python:
|
if fallback_to_python:
|
||||||
# This is the part where if we haven't found anything, just return
|
# This is the part where if we haven't found anything, just return
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def _resolve_forward_references(context, value_set):
|
|||||||
yield value
|
yield value
|
||||||
|
|
||||||
|
|
||||||
class _AbstractGenericManager(object):
|
class _AbstractGenericManager:
|
||||||
def get_index_and_execute(self, index):
|
def get_index_and_execute(self, index):
|
||||||
try:
|
try:
|
||||||
return self[index].execute_annotation()
|
return self[index].execute_annotation()
|
||||||
@@ -31,6 +31,9 @@ class _AbstractGenericManager(object):
|
|||||||
debug.warning('No param #%s found for annotation %s', index, self)
|
debug.warning('No param #%s found for annotation %s', index, self)
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|
||||||
|
def get_type_hint(self):
|
||||||
|
return '[%s]' % ', '.join(t.get_type_hint(add_class_info=False) for t in self.to_tuple())
|
||||||
|
|
||||||
|
|
||||||
class LazyGenericManager(_AbstractGenericManager):
|
class LazyGenericManager(_AbstractGenericManager):
|
||||||
def __init__(self, context_of_index, index_value):
|
def __init__(self, context_of_index, index_value):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class StubModuleValue(ModuleValue):
|
|||||||
_module_name_class = StubModuleName
|
_module_name_class = StubModuleName
|
||||||
|
|
||||||
def __init__(self, non_stub_value_set, *args, **kwargs):
|
def __init__(self, non_stub_value_set, *args, **kwargs):
|
||||||
super(StubModuleValue, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.non_stub_value_set = non_stub_value_set
|
self.non_stub_value_set = non_stub_value_set
|
||||||
|
|
||||||
def is_stub(self):
|
def is_stub(self):
|
||||||
@@ -30,7 +30,7 @@ class StubModuleValue(ModuleValue):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
names.update(method())
|
names.update(method())
|
||||||
names.update(super(StubModuleValue, self).sub_modules_dict())
|
names.update(super().sub_modules_dict())
|
||||||
return names
|
return names
|
||||||
|
|
||||||
def _get_stub_filters(self, origin_scope):
|
def _get_stub_filters(self, origin_scope):
|
||||||
@@ -40,14 +40,11 @@ class StubModuleValue(ModuleValue):
|
|||||||
)] + list(self.iter_star_filters())
|
)] + list(self.iter_star_filters())
|
||||||
|
|
||||||
def get_filters(self, origin_scope=None):
|
def get_filters(self, origin_scope=None):
|
||||||
filters = super(StubModuleValue, self).get_filters(origin_scope)
|
filters = super().get_filters(origin_scope)
|
||||||
next(filters) # Ignore the first filter and replace it with our own
|
next(filters, None) # Ignore the first filter and replace it with our own
|
||||||
stub_filters = self._get_stub_filters(origin_scope=origin_scope)
|
stub_filters = self._get_stub_filters(origin_scope=origin_scope)
|
||||||
for f in stub_filters:
|
yield from stub_filters
|
||||||
yield f
|
yield from filters
|
||||||
|
|
||||||
for f in filters:
|
|
||||||
yield f
|
|
||||||
|
|
||||||
def _as_context(self):
|
def _as_context(self):
|
||||||
return StubModuleContext(self)
|
return StubModuleContext(self)
|
||||||
@@ -57,15 +54,16 @@ class StubModuleContext(ModuleContext):
|
|||||||
def get_filters(self, until_position=None, origin_scope=None):
|
def get_filters(self, until_position=None, origin_scope=None):
|
||||||
# Make sure to ignore the position, because positions are not relevant
|
# Make sure to ignore the position, because positions are not relevant
|
||||||
# for stubs.
|
# for stubs.
|
||||||
return super(StubModuleContext, self).get_filters(origin_scope=origin_scope)
|
return super().get_filters(origin_scope=origin_scope)
|
||||||
|
|
||||||
|
|
||||||
class TypingModuleWrapper(StubModuleValue):
|
class TypingModuleWrapper(StubModuleValue):
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
filters = super(TypingModuleWrapper, self).get_filters(*args, **kwargs)
|
filters = super().get_filters(*args, **kwargs)
|
||||||
yield TypingModuleFilterWrapper(next(filters))
|
f = next(filters, None)
|
||||||
for f in filters:
|
assert f is not None
|
||||||
yield f
|
yield TypingModuleFilterWrapper(f)
|
||||||
|
yield from filters
|
||||||
|
|
||||||
def _as_context(self):
|
def _as_context(self):
|
||||||
return TypingModuleContext(self)
|
return TypingModuleContext(self)
|
||||||
@@ -73,17 +71,16 @@ class TypingModuleWrapper(StubModuleValue):
|
|||||||
|
|
||||||
class TypingModuleContext(ModuleContext):
|
class TypingModuleContext(ModuleContext):
|
||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
filters = super(TypingModuleContext, self).get_filters(*args, **kwargs)
|
filters = super().get_filters(*args, **kwargs)
|
||||||
yield TypingModuleFilterWrapper(next(filters))
|
yield TypingModuleFilterWrapper(next(filters, None))
|
||||||
for f in filters:
|
yield from filters
|
||||||
yield f
|
|
||||||
|
|
||||||
|
|
||||||
class StubFilter(ParserTreeFilter):
|
class StubFilter(ParserTreeFilter):
|
||||||
name_class = StubName
|
name_class = StubName
|
||||||
|
|
||||||
def _is_name_reachable(self, name):
|
def _is_name_reachable(self, name):
|
||||||
if not super(StubFilter, self)._is_name_reachable(name):
|
if not super()._is_name_reachable(name):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Imports in stub files are only public if they have an "as"
|
# Imports in stub files are only public if they have an "as"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from jedi._compatibility import unicode, force_unicode
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
from jedi.inference.base_value import ValueSet, NO_VALUES, ValueWrapper
|
||||||
from jedi.inference.gradual.base import BaseTypingValue
|
from jedi.inference.gradual.base import BaseTypingValue
|
||||||
|
|
||||||
|
|
||||||
class TypeVarClass(BaseTypingValue):
|
class TypeVarClass(ValueWrapper):
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
unpacked = arguments.unpack()
|
unpacked = arguments.unpack()
|
||||||
|
|
||||||
@@ -18,9 +17,9 @@ class TypeVarClass(BaseTypingValue):
|
|||||||
return ValueSet([TypeVar.create_cached(
|
return ValueSet([TypeVar.create_cached(
|
||||||
self.inference_state,
|
self.inference_state,
|
||||||
self.parent_context,
|
self.parent_context,
|
||||||
self._tree_name,
|
tree_name=self.tree_node.name,
|
||||||
var_name,
|
var_name=var_name,
|
||||||
unpacked
|
unpacked_args=unpacked,
|
||||||
)])
|
)])
|
||||||
|
|
||||||
def _find_string_name(self, lazy_value):
|
def _find_string_name(self, lazy_value):
|
||||||
@@ -40,17 +39,14 @@ class TypeVarClass(BaseTypingValue):
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
safe_value = method(default=None)
|
safe_value = method(default=None)
|
||||||
if self.inference_state.environment.version_info.major == 2:
|
if isinstance(safe_value, str):
|
||||||
if isinstance(safe_value, bytes):
|
|
||||||
return force_unicode(safe_value)
|
|
||||||
if isinstance(safe_value, (str, unicode)):
|
|
||||||
return safe_value
|
return safe_value
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TypeVar(BaseTypingValue):
|
class TypeVar(BaseTypingValue):
|
||||||
def __init__(self, parent_context, tree_name, var_name, unpacked_args):
|
def __init__(self, parent_context, tree_name, var_name, unpacked_args):
|
||||||
super(TypeVar, self).__init__(parent_context, tree_name)
|
super().__init__(parent_context, tree_name)
|
||||||
self._var_name = var_name
|
self._var_name = var_name
|
||||||
|
|
||||||
self._constraints_lazy_values = []
|
self._constraints_lazy_values = []
|
||||||
@@ -102,10 +98,30 @@ class TypeVar(BaseTypingValue):
|
|||||||
else:
|
else:
|
||||||
if found:
|
if found:
|
||||||
return found
|
return found
|
||||||
return self._get_classes() or ValueSet({self})
|
return ValueSet({self})
|
||||||
|
|
||||||
def execute_annotation(self):
|
def execute_annotation(self):
|
||||||
return self._get_classes().execute_annotation()
|
return self._get_classes().execute_annotation()
|
||||||
|
|
||||||
|
def infer_type_vars(self, value_set):
|
||||||
|
def iterate():
|
||||||
|
for v in value_set:
|
||||||
|
cls = v.py__class__()
|
||||||
|
if v.is_function() or v.is_class():
|
||||||
|
cls = TypeWrapper(cls, v)
|
||||||
|
yield cls
|
||||||
|
|
||||||
|
annotation_name = self.py__name__()
|
||||||
|
return {annotation_name: ValueSet(iterate())}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.py__name__())
|
return '<%s: %s>' % (self.__class__.__name__, self.py__name__())
|
||||||
|
|
||||||
|
|
||||||
|
class TypeWrapper(ValueWrapper):
|
||||||
|
def __init__(self, wrapped_value, original_value):
|
||||||
|
super().__init__(wrapped_value)
|
||||||
|
self._original_value = original_value
|
||||||
|
|
||||||
|
def execute_annotation(self):
|
||||||
|
return ValueSet({self._original_value})
|
||||||
@@ -1,74 +1,81 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from collections import namedtuple
|
||||||
|
from typing import Dict, Mapping, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jedi import settings
|
||||||
from jedi.file_io import FileIO
|
from jedi.file_io import FileIO
|
||||||
from jedi._compatibility import FileNotFoundError, cast_path
|
from jedi._compatibility import cast_path
|
||||||
from jedi.parser_utils import get_cached_code_lines
|
from jedi.parser_utils import get_cached_code_lines
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
from jedi.inference.base_value import ValueSet, NO_VALUES
|
||||||
from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue
|
from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue
|
||||||
from jedi.inference.value import ModuleValue
|
from jedi.inference.value import ModuleValue
|
||||||
|
|
||||||
_jedi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
_jedi_path = Path(__file__).parent.parent.parent
|
||||||
TYPESHED_PATH = os.path.join(_jedi_path, 'third_party', 'typeshed')
|
TYPESHED_PATH = _jedi_path.joinpath('third_party', 'typeshed')
|
||||||
|
DJANGO_INIT_PATH = _jedi_path.joinpath('third_party', 'django-stubs',
|
||||||
|
'django-stubs', '__init__.pyi')
|
||||||
|
|
||||||
_IMPORT_MAP = dict(
|
_IMPORT_MAP = dict(
|
||||||
_collections='collections',
|
_collections='collections',
|
||||||
_socket='socket',
|
_socket='socket',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PathInfo = namedtuple('PathInfo', 'path is_third_party')
|
||||||
|
|
||||||
def _merge_create_stub_map(directories):
|
|
||||||
|
def _merge_create_stub_map(path_infos):
|
||||||
map_ = {}
|
map_ = {}
|
||||||
for directory in directories:
|
for directory_path_info in path_infos:
|
||||||
map_.update(_create_stub_map(directory))
|
map_.update(_create_stub_map(directory_path_info))
|
||||||
return map_
|
return map_
|
||||||
|
|
||||||
|
|
||||||
def _create_stub_map(directory):
|
def _create_stub_map(directory_path_info):
|
||||||
"""
|
"""
|
||||||
Create a mapping of an importable name in Python to a stub file.
|
Create a mapping of an importable name in Python to a stub file.
|
||||||
"""
|
"""
|
||||||
def generate():
|
def generate():
|
||||||
try:
|
try:
|
||||||
listed = os.listdir(directory)
|
listed = os.listdir(directory_path_info.path)
|
||||||
except (FileNotFoundError, OSError):
|
except (FileNotFoundError, NotADirectoryError):
|
||||||
# OSError is Python 2
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for entry in listed:
|
for entry in listed:
|
||||||
entry = cast_path(entry)
|
entry = cast_path(entry)
|
||||||
path = os.path.join(directory, entry)
|
path = os.path.join(directory_path_info.path, entry)
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
init = os.path.join(path, '__init__.pyi')
|
init = os.path.join(path, '__init__.pyi')
|
||||||
if os.path.isfile(init):
|
if os.path.isfile(init):
|
||||||
yield entry, init
|
yield entry, PathInfo(init, directory_path_info.is_third_party)
|
||||||
elif entry.endswith('.pyi') and os.path.isfile(path):
|
elif entry.endswith('.pyi') and os.path.isfile(path):
|
||||||
name = entry[:-4]
|
name = entry[:-4]
|
||||||
if name != '__init__':
|
if name != '__init__':
|
||||||
yield name, path
|
yield name, PathInfo(path, directory_path_info.is_third_party)
|
||||||
|
|
||||||
# Create a dictionary from the tuple generator.
|
# Create a dictionary from the tuple generator.
|
||||||
return dict(generate())
|
return dict(generate())
|
||||||
|
|
||||||
|
|
||||||
def _get_typeshed_directories(version_info):
|
def _get_typeshed_directories(version_info):
|
||||||
check_version_list = ['2and3', str(version_info.major)]
|
check_version_list = ['2and3', '3']
|
||||||
for base in ['stdlib', 'third_party']:
|
for base in ['stdlib', 'third_party']:
|
||||||
base = os.path.join(TYPESHED_PATH, base)
|
base_path = TYPESHED_PATH.joinpath(base)
|
||||||
base_list = os.listdir(base)
|
base_list = os.listdir(base_path)
|
||||||
for base_list_entry in base_list:
|
for base_list_entry in base_list:
|
||||||
match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
|
match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
if int(match.group(1)) == version_info.major \
|
if match.group(1) == '3' and int(match.group(2)) <= version_info.minor:
|
||||||
and int(match.group(2)) <= version_info.minor:
|
|
||||||
check_version_list.append(base_list_entry)
|
check_version_list.append(base_list_entry)
|
||||||
|
|
||||||
for check_version in check_version_list:
|
for check_version in check_version_list:
|
||||||
yield os.path.join(base, check_version)
|
is_third_party = base != 'stdlib'
|
||||||
|
yield PathInfo(str(base_path.joinpath(check_version)), is_third_party)
|
||||||
|
|
||||||
|
|
||||||
_version_cache = {}
|
_version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {}
|
||||||
|
|
||||||
|
|
||||||
def _cache_stub_file_map(version_info):
|
def _cache_stub_file_map(version_info):
|
||||||
@@ -102,12 +109,9 @@ def import_module_decorator(func):
|
|||||||
# ``os.path``, because it's a very important one in Python
|
# ``os.path``, because it's a very important one in Python
|
||||||
# that is being achieved by messing with ``sys.modules`` in
|
# that is being achieved by messing with ``sys.modules`` in
|
||||||
# ``os``.
|
# ``os``.
|
||||||
python_parent = next(iter(parent_module_values))
|
|
||||||
if python_parent is None:
|
|
||||||
python_parent, = inference_state.import_module(('os',), prefer_stubs=False)
|
|
||||||
python_value_set = ValueSet.from_sets(
|
python_value_set = ValueSet.from_sets(
|
||||||
func(inference_state, (n,), None, sys_path,)
|
func(inference_state, (n,), None, sys_path,)
|
||||||
for n in [u'posixpath', u'ntpath', u'macpath', u'os2emxpath']
|
for n in ['posixpath', 'ntpath', 'macpath', 'os2emxpath']
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
python_value_set = ValueSet.from_sets(
|
python_value_set = ValueSet.from_sets(
|
||||||
@@ -116,11 +120,11 @@ def import_module_decorator(func):
|
|||||||
)
|
)
|
||||||
inference_state.module_cache.add(import_names, python_value_set)
|
inference_state.module_cache.add(import_names, python_value_set)
|
||||||
|
|
||||||
if not prefer_stubs:
|
if not prefer_stubs or import_names[0] in settings.auto_import_modules:
|
||||||
return python_value_set
|
return python_value_set
|
||||||
|
|
||||||
stub = _try_to_load_stub_cached(inference_state, import_names, python_value_set,
|
stub = try_to_load_stub_cached(inference_state, import_names, python_value_set,
|
||||||
parent_module_value, sys_path)
|
parent_module_value, sys_path)
|
||||||
if stub is not None:
|
if stub is not None:
|
||||||
return ValueSet([stub])
|
return ValueSet([stub])
|
||||||
return python_value_set
|
return python_value_set
|
||||||
@@ -128,7 +132,10 @@ def import_module_decorator(func):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def _try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
|
def try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
|
||||||
|
if import_names is None:
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return inference_state.stub_module_cache[import_names]
|
return inference_state.stub_module_cache[import_names]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -152,7 +159,7 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
|
|||||||
"""
|
"""
|
||||||
if parent_module_value is None and len(import_names) > 1:
|
if parent_module_value is None and len(import_names) > 1:
|
||||||
try:
|
try:
|
||||||
parent_module_value = _try_to_load_stub_cached(
|
parent_module_value = try_to_load_stub_cached(
|
||||||
inference_state, import_names[:-1], NO_VALUES,
|
inference_state, import_names[:-1], NO_VALUES,
|
||||||
parent_module_value=None, sys_path=sys_path)
|
parent_module_value=None, sys_path=sys_path)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -172,6 +179,13 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
|
|||||||
)
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
return m
|
return m
|
||||||
|
if import_names[0] == 'django' and python_value_set:
|
||||||
|
return _try_to_load_stub_from_file(
|
||||||
|
inference_state,
|
||||||
|
python_value_set,
|
||||||
|
file_io=FileIO(str(DJANGO_INIT_PATH)),
|
||||||
|
import_names=import_names,
|
||||||
|
)
|
||||||
|
|
||||||
# 2. Try to load pyi files next to py files.
|
# 2. Try to load pyi files next to py files.
|
||||||
for c in python_value_set:
|
for c in python_value_set:
|
||||||
@@ -184,8 +198,8 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
|
|||||||
file_paths = []
|
file_paths = []
|
||||||
if c.is_namespace():
|
if c.is_namespace():
|
||||||
file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()]
|
file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()]
|
||||||
elif file_path is not None and file_path.endswith('.py'):
|
elif file_path is not None and file_path.suffix == '.py':
|
||||||
file_paths = [file_path + 'i']
|
file_paths = [str(file_path) + 'i']
|
||||||
|
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
m = _try_to_load_stub_from_file(
|
m = _try_to_load_stub_from_file(
|
||||||
@@ -239,38 +253,49 @@ def _load_from_typeshed(inference_state, python_value_set, parent_module_value,
|
|||||||
# Only if it's a package (= a folder) something can be
|
# Only if it's a package (= a folder) something can be
|
||||||
# imported.
|
# imported.
|
||||||
return None
|
return None
|
||||||
path = parent_module_value.py__path__()
|
paths = parent_module_value.py__path__()
|
||||||
map_ = _merge_create_stub_map(path)
|
# Once the initial package has been loaded, the sub packages will
|
||||||
|
# always be loaded, regardless if they are there or not. This makes
|
||||||
|
# sense, IMO, because stubs take preference, even if the original
|
||||||
|
# library doesn't provide a module (it could be dynamic). ~dave
|
||||||
|
map_ = _merge_create_stub_map([PathInfo(p, is_third_party=False) for p in paths])
|
||||||
|
|
||||||
if map_ is not None:
|
if map_ is not None:
|
||||||
path = map_.get(import_name)
|
path_info = map_.get(import_name)
|
||||||
if path is not None:
|
if path_info is not None and (not path_info.is_third_party or python_value_set):
|
||||||
return _try_to_load_stub_from_file(
|
return _try_to_load_stub_from_file(
|
||||||
inference_state,
|
inference_state,
|
||||||
python_value_set,
|
python_value_set,
|
||||||
file_io=FileIO(path),
|
file_io=FileIO(path_info.path),
|
||||||
import_names=import_names,
|
import_names=import_names,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names):
|
def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names):
|
||||||
try:
|
try:
|
||||||
stub_module_node = inference_state.parse(
|
stub_module_node = parse_stub_module(inference_state, file_io)
|
||||||
file_io=file_io,
|
except OSError:
|
||||||
cache=True,
|
|
||||||
use_latest_grammar=True
|
|
||||||
)
|
|
||||||
except (OSError, IOError): # IOError is Python 2 only
|
|
||||||
# The file that you're looking for doesn't exist (anymore).
|
# The file that you're looking for doesn't exist (anymore).
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return create_stub_module(
|
return create_stub_module(
|
||||||
inference_state, python_value_set, stub_module_node, file_io,
|
inference_state, inference_state.latest_grammar, python_value_set,
|
||||||
import_names
|
stub_module_node, file_io, import_names
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_stub_module(inference_state, python_value_set, stub_module_node, file_io, import_names):
|
def parse_stub_module(inference_state, file_io):
|
||||||
|
return inference_state.parse(
|
||||||
|
file_io=file_io,
|
||||||
|
cache=True,
|
||||||
|
diff_cache=settings.fast_parser,
|
||||||
|
cache_path=settings.cache_directory,
|
||||||
|
use_latest_grammar=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_stub_module(inference_state, grammar, python_value_set,
|
||||||
|
stub_module_node, file_io, import_names):
|
||||||
if import_names == ('typing',):
|
if import_names == ('typing',):
|
||||||
module_cls = TypingModuleWrapper
|
module_cls = TypingModuleWrapper
|
||||||
else:
|
else:
|
||||||
@@ -282,7 +307,7 @@ def create_stub_module(inference_state, python_value_set, stub_module_node, file
|
|||||||
string_names=import_names,
|
string_names=import_names,
|
||||||
# The code was loaded with latest_grammar, so use
|
# The code was loaded with latest_grammar, so use
|
||||||
# that.
|
# that.
|
||||||
code_lines=get_cached_code_lines(inference_state.latest_grammar, file_io.path),
|
code_lines=get_cached_code_lines(grammar, file_io.path),
|
||||||
is_package=file_name == '__init__.pyi',
|
is_package=file_name == '__init__.pyi',
|
||||||
)
|
)
|
||||||
return stub_module_value
|
return stub_module_value
|
||||||
@@ -5,16 +5,19 @@ values.
|
|||||||
|
|
||||||
This file deals with all the typing.py cases.
|
This file deals with all the typing.py cases.
|
||||||
"""
|
"""
|
||||||
|
import itertools
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.compiled import builtin_from_name
|
from jedi.inference.compiled import builtin_from_name, create_simple_object
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \
|
from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \
|
||||||
LazyValueWrapper
|
LazyValueWrapper, ValueWrapper
|
||||||
from jedi.inference.lazy_value import LazyKnownValues
|
from jedi.inference.lazy_value import LazyKnownValues
|
||||||
from jedi.inference.arguments import repack_with_argument_clinic
|
from jedi.inference.arguments import repack_with_argument_clinic
|
||||||
from jedi.inference.filters import FilterWrapper
|
from jedi.inference.filters import FilterWrapper
|
||||||
from jedi.inference.names import NameWrapper, ValueName
|
from jedi.inference.names import NameWrapper, ValueName
|
||||||
from jedi.inference.value.klass import ClassMixin
|
from jedi.inference.value.klass import ClassMixin
|
||||||
from jedi.inference.gradual.base import BaseTypingValue, BaseTypingValueWithGenerics
|
from jedi.inference.gradual.base import BaseTypingValue, \
|
||||||
|
BaseTypingClassWithGenerics, BaseTypingInstance
|
||||||
from jedi.inference.gradual.type_var import TypeVarClass
|
from jedi.inference.gradual.type_var import TypeVarClass
|
||||||
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
|
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
|
||||||
|
|
||||||
@@ -60,43 +63,41 @@ class TypingModuleName(NameWrapper):
|
|||||||
# have any effects there (because it's never executed).
|
# have any effects there (because it's never executed).
|
||||||
return
|
return
|
||||||
elif name == 'TypeVar':
|
elif name == 'TypeVar':
|
||||||
yield TypeVarClass.create_cached(
|
cls, = self._wrapped_name.infer()
|
||||||
inference_state, self.parent_context, self.tree_name)
|
yield TypeVarClass.create_cached(inference_state, cls)
|
||||||
elif name == 'Any':
|
elif name == 'Any':
|
||||||
yield Any.create_cached(
|
yield AnyClass.create_cached(
|
||||||
inference_state, self.parent_context, self.tree_name)
|
inference_state, self.parent_context, self.tree_name)
|
||||||
elif name == 'TYPE_CHECKING':
|
elif name == 'TYPE_CHECKING':
|
||||||
# This is needed for e.g. imports that are only available for type
|
# This is needed for e.g. imports that are only available for type
|
||||||
# checking or are in cycles. The user can then check this variable.
|
# checking or are in cycles. The user can then check this variable.
|
||||||
yield builtin_from_name(inference_state, u'True')
|
yield builtin_from_name(inference_state, 'True')
|
||||||
elif name == 'overload':
|
elif name == 'overload':
|
||||||
yield OverloadFunction.create_cached(
|
yield OverloadFunction.create_cached(
|
||||||
inference_state, self.parent_context, self.tree_name)
|
inference_state, self.parent_context, self.tree_name)
|
||||||
elif name == 'NewType':
|
elif name == 'NewType':
|
||||||
yield NewTypeFunction.create_cached(
|
v, = self._wrapped_name.infer()
|
||||||
inference_state, self.parent_context, self.tree_name)
|
yield NewTypeFunction.create_cached(inference_state, v)
|
||||||
elif name == 'cast':
|
elif name == 'cast':
|
||||||
yield CastFunction.create_cached(
|
cast_fn, = self._wrapped_name.infer()
|
||||||
inference_state, self.parent_context, self.tree_name)
|
yield CastFunction.create_cached(inference_state, cast_fn)
|
||||||
elif name == 'TypedDict':
|
elif name == 'TypedDict':
|
||||||
# TODO doesn't even exist in typeshed/typing.py, yet. But will be
|
# TODO doesn't even exist in typeshed/typing.py, yet. But will be
|
||||||
# added soon.
|
# added soon.
|
||||||
pass
|
yield TypedDictClass.create_cached(
|
||||||
elif name in ('no_type_check', 'no_type_check_decorator'):
|
inference_state, self.parent_context, self.tree_name)
|
||||||
# This is not necessary, as long as we are not doing type checking.
|
|
||||||
for c in self._wrapped_name.infer(): # Fuck my life Python 2
|
|
||||||
yield c
|
|
||||||
else:
|
else:
|
||||||
# Everything else shouldn't be relevant for type checking.
|
# Not necessary, as long as we are not doing type checking:
|
||||||
for c in self._wrapped_name.infer(): # Fuck my life Python 2
|
# no_type_check & no_type_check_decorator
|
||||||
yield c
|
# Everything else shouldn't be relevant...
|
||||||
|
yield from self._wrapped_name.infer()
|
||||||
|
|
||||||
|
|
||||||
class TypingModuleFilterWrapper(FilterWrapper):
|
class TypingModuleFilterWrapper(FilterWrapper):
|
||||||
name_wrapper_class = TypingModuleName
|
name_wrapper_class = TypingModuleName
|
||||||
|
|
||||||
|
|
||||||
class TypingValueWithIndex(BaseTypingValueWithGenerics):
|
class ProxyWithGenerics(BaseTypingClassWithGenerics):
|
||||||
def execute_annotation(self):
|
def execute_annotation(self):
|
||||||
string_name = self._tree_name.value
|
string_name = self._tree_name.value
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics):
|
|||||||
# Optional is basically just saying it's either None or the actual
|
# Optional is basically just saying it's either None or the actual
|
||||||
# type.
|
# type.
|
||||||
return self.gather_annotation_classes().execute_annotation() \
|
return self.gather_annotation_classes().execute_annotation() \
|
||||||
| ValueSet([builtin_from_name(self.inference_state, u'None')])
|
| ValueSet([builtin_from_name(self.inference_state, 'None')])
|
||||||
elif string_name == 'Type':
|
elif string_name == 'Type':
|
||||||
# The type is actually already given in the index_value
|
# The type is actually already given in the index_value
|
||||||
return self._generics_manager[0]
|
return self._generics_manager[0]
|
||||||
@@ -125,6 +126,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics):
|
|||||||
cls = mapped[string_name]
|
cls = mapped[string_name]
|
||||||
return ValueSet([cls(
|
return ValueSet([cls(
|
||||||
self.parent_context,
|
self.parent_context,
|
||||||
|
self,
|
||||||
self._tree_name,
|
self._tree_name,
|
||||||
generics_manager=self._generics_manager,
|
generics_manager=self._generics_manager,
|
||||||
)])
|
)])
|
||||||
@@ -133,15 +135,33 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics):
|
|||||||
return ValueSet.from_sets(self._generics_manager.to_tuple())
|
return ValueSet.from_sets(self._generics_manager.to_tuple())
|
||||||
|
|
||||||
def _create_instance_with_generics(self, generics_manager):
|
def _create_instance_with_generics(self, generics_manager):
|
||||||
return TypingValueWithIndex(
|
return ProxyWithGenerics(
|
||||||
self.parent_context,
|
self.parent_context,
|
||||||
self._tree_name,
|
self._tree_name,
|
||||||
generics_manager
|
generics_manager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def infer_type_vars(self, value_set):
|
||||||
|
annotation_generics = self.get_generics()
|
||||||
|
|
||||||
|
if not annotation_generics:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
annotation_name = self.py__name__()
|
||||||
|
if annotation_name == 'Optional':
|
||||||
|
# Optional[T] is equivalent to Union[T, None]. In Jedi unions
|
||||||
|
# are represented by members within a ValueSet, so we extract
|
||||||
|
# the T from the Optional[T] by removing the None value.
|
||||||
|
none = builtin_from_name(self.inference_state, 'None')
|
||||||
|
return annotation_generics[0].infer_type_vars(
|
||||||
|
value_set.filter(lambda x: x != none),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class ProxyTypingValue(BaseTypingValue):
|
class ProxyTypingValue(BaseTypingValue):
|
||||||
index_class = TypingValueWithIndex
|
index_class = ProxyWithGenerics
|
||||||
|
|
||||||
def with_generics(self, generics_tuple):
|
def with_generics(self, generics_tuple):
|
||||||
return self.index_class.create_cached(
|
return self.index_class.create_cached(
|
||||||
@@ -179,12 +199,45 @@ class _TypingClassMixin(ClassMixin):
|
|||||||
return ValueName(self, self._tree_name)
|
return ValueName(self, self._tree_name)
|
||||||
|
|
||||||
|
|
||||||
class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex):
|
class TypingClassWithGenerics(ProxyWithGenerics, _TypingClassMixin):
|
||||||
pass
|
def infer_type_vars(self, value_set):
|
||||||
|
type_var_dict = {}
|
||||||
|
annotation_generics = self.get_generics()
|
||||||
|
|
||||||
|
if not annotation_generics:
|
||||||
|
return type_var_dict
|
||||||
|
|
||||||
|
annotation_name = self.py__name__()
|
||||||
|
if annotation_name == 'Type':
|
||||||
|
return annotation_generics[0].infer_type_vars(
|
||||||
|
# This is basically a trick to avoid extra code: We execute the
|
||||||
|
# incoming classes to be able to use the normal code for type
|
||||||
|
# var inference.
|
||||||
|
value_set.execute_annotation(),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif annotation_name == 'Callable':
|
||||||
|
if len(annotation_generics) == 2:
|
||||||
|
return annotation_generics[1].infer_type_vars(
|
||||||
|
value_set.execute_annotation(),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif annotation_name == 'Tuple':
|
||||||
|
tuple_annotation, = self.execute_annotation()
|
||||||
|
return tuple_annotation.infer_type_vars(value_set)
|
||||||
|
|
||||||
|
return type_var_dict
|
||||||
|
|
||||||
|
def _create_instance_with_generics(self, generics_manager):
|
||||||
|
return TypingClassWithGenerics(
|
||||||
|
self.parent_context,
|
||||||
|
self._tree_name,
|
||||||
|
generics_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProxyTypingClassValue(_TypingClassMixin, ProxyTypingValue):
|
class ProxyTypingClassValue(ProxyTypingValue, _TypingClassMixin):
|
||||||
index_class = TypingClassValueWithIndex
|
index_class = TypingClassWithGenerics
|
||||||
|
|
||||||
|
|
||||||
class TypeAlias(LazyValueWrapper):
|
class TypeAlias(LazyValueWrapper):
|
||||||
@@ -206,8 +259,6 @@ class TypeAlias(LazyValueWrapper):
|
|||||||
|
|
||||||
def _get_wrapped_value(self):
|
def _get_wrapped_value(self):
|
||||||
module_name, class_name = self._actual.split('.')
|
module_name, class_name = self._actual.split('.')
|
||||||
if self.inference_state.environment.version_info.major == 2 and module_name == 'builtins':
|
|
||||||
module_name = '__builtin__'
|
|
||||||
|
|
||||||
# TODO use inference_state.import_module?
|
# TODO use inference_state.import_module?
|
||||||
from jedi.inference.imports import Importer
|
from jedi.inference.imports import Importer
|
||||||
@@ -223,8 +274,11 @@ class TypeAlias(LazyValueWrapper):
|
|||||||
def gather_annotation_classes(self):
|
def gather_annotation_classes(self):
|
||||||
return ValueSet([self._get_wrapped_value()])
|
return ValueSet([self._get_wrapped_value()])
|
||||||
|
|
||||||
|
def get_signatures(self):
|
||||||
|
return []
|
||||||
|
|
||||||
class Callable(BaseTypingValueWithGenerics):
|
|
||||||
|
class Callable(BaseTypingInstance):
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
"""
|
"""
|
||||||
def x() -> Callable[[Callable[..., _T]], _T]: ...
|
def x() -> Callable[[Callable[..., _T]], _T]: ...
|
||||||
@@ -241,12 +295,7 @@ class Callable(BaseTypingValueWithGenerics):
|
|||||||
return infer_return_for_callable(arguments, param_values, result_values)
|
return infer_return_for_callable(arguments, param_values, result_values)
|
||||||
|
|
||||||
|
|
||||||
class Tuple(LazyValueWrapper):
|
class Tuple(BaseTypingInstance):
|
||||||
def __init__(self, parent_context, name, generics_manager):
|
|
||||||
self.inference_state = parent_context.inference_state
|
|
||||||
self.parent_context = parent_context
|
|
||||||
self._generics_manager = generics_manager
|
|
||||||
|
|
||||||
def _is_homogenous(self):
|
def _is_homogenous(self):
|
||||||
# To specify a variable-length tuple of homogeneous type, Tuple[T, ...]
|
# To specify a variable-length tuple of homogeneous type, Tuple[T, ...]
|
||||||
# is used.
|
# is used.
|
||||||
@@ -282,16 +331,60 @@ class Tuple(LazyValueWrapper):
|
|||||||
.py__getattribute__('tuple').execute_annotation()
|
.py__getattribute__('tuple').execute_annotation()
|
||||||
return tuple_
|
return tuple_
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._wrapped_value.name
|
||||||
|
|
||||||
class Generic(BaseTypingValueWithGenerics):
|
def infer_type_vars(self, value_set):
|
||||||
|
# Circular
|
||||||
|
from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts
|
||||||
|
|
||||||
|
value_set = value_set.filter(
|
||||||
|
lambda x: x.py__name__().lower() == 'tuple',
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._is_homogenous():
|
||||||
|
# The parameter annotation is of the form `Tuple[T, ...]`,
|
||||||
|
# so we treat the incoming tuple like a iterable sequence
|
||||||
|
# rather than a positional container of elements.
|
||||||
|
return self._class_value.get_generics()[0].infer_type_vars(
|
||||||
|
value_set.merge_types_of_iterate(),
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# The parameter annotation has only explicit type parameters
|
||||||
|
# (e.g: `Tuple[T]`, `Tuple[T, U]`, `Tuple[T, U, V]`, etc.) so we
|
||||||
|
# treat the incoming values as needing to match the annotation
|
||||||
|
# exactly, just as we would for non-tuple annotations.
|
||||||
|
|
||||||
|
type_var_dict = {}
|
||||||
|
for element in value_set:
|
||||||
|
try:
|
||||||
|
method = element.get_annotated_class_object
|
||||||
|
except AttributeError:
|
||||||
|
# This might still happen, because the tuple name matching
|
||||||
|
# above is not 100% correct, so just catch the remaining
|
||||||
|
# cases here.
|
||||||
|
continue
|
||||||
|
|
||||||
|
py_class = method()
|
||||||
|
merge_type_var_dicts(
|
||||||
|
type_var_dict,
|
||||||
|
merge_pairwise_generics(self._class_value, py_class),
|
||||||
|
)
|
||||||
|
|
||||||
|
return type_var_dict
|
||||||
|
|
||||||
|
|
||||||
|
class Generic(BaseTypingInstance):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Protocol(BaseTypingValueWithGenerics):
|
class Protocol(BaseTypingInstance):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Any(BaseTypingValue):
|
class AnyClass(BaseTypingValue):
|
||||||
def execute_annotation(self):
|
def execute_annotation(self):
|
||||||
debug.warning('Used Any - returned no results')
|
debug.warning('Used Any - returned no results')
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
@@ -304,7 +397,7 @@ class OverloadFunction(BaseTypingValue):
|
|||||||
return func_value_set
|
return func_value_set
|
||||||
|
|
||||||
|
|
||||||
class NewTypeFunction(BaseTypingValue):
|
class NewTypeFunction(ValueWrapper):
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
ordered_args = arguments.unpack()
|
ordered_args = arguments.unpack()
|
||||||
next(ordered_args, (None, None))
|
next(ordered_args, (None, None))
|
||||||
@@ -322,10 +415,14 @@ class NewTypeFunction(BaseTypingValue):
|
|||||||
|
|
||||||
class NewType(Value):
|
class NewType(Value):
|
||||||
def __init__(self, inference_state, parent_context, tree_node, type_value_set):
|
def __init__(self, inference_state, parent_context, tree_node, type_value_set):
|
||||||
super(NewType, self).__init__(inference_state, parent_context)
|
super().__init__(inference_state, parent_context)
|
||||||
self._type_value_set = type_value_set
|
self._type_value_set = type_value_set
|
||||||
self.tree_node = tree_node
|
self.tree_node = tree_node
|
||||||
|
|
||||||
|
def py__class__(self):
|
||||||
|
c, = self._type_value_set.py__class__()
|
||||||
|
return c
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
return self._type_value_set.execute_annotation()
|
return self._type_value_set.execute_annotation()
|
||||||
|
|
||||||
@@ -335,7 +432,51 @@ class NewType(Value):
|
|||||||
return CompiledValueName(self, 'NewType')
|
return CompiledValueName(self, 'NewType')
|
||||||
|
|
||||||
|
|
||||||
class CastFunction(BaseTypingValue):
|
class CastFunction(ValueWrapper):
|
||||||
@repack_with_argument_clinic('type, object, /')
|
@repack_with_argument_clinic('type, object, /')
|
||||||
def py__call__(self, type_value_set, object_value_set):
|
def py__call__(self, type_value_set, object_value_set):
|
||||||
return type_value_set.execute_annotation()
|
return type_value_set.execute_annotation()
|
||||||
|
|
||||||
|
|
||||||
|
class TypedDictClass(BaseTypingValue):
|
||||||
|
"""
|
||||||
|
This class has no responsibilities and is just here to make sure that typed
|
||||||
|
dicts can be identified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TypedDict(LazyValueWrapper):
|
||||||
|
"""Represents the instance version of ``TypedDictClass``."""
|
||||||
|
def __init__(self, definition_class):
|
||||||
|
self.inference_state = definition_class.inference_state
|
||||||
|
self.parent_context = definition_class.parent_context
|
||||||
|
self.tree_node = definition_class.tree_node
|
||||||
|
self._definition_class = definition_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return ValueName(self, self.tree_node.name)
|
||||||
|
|
||||||
|
def py__simple_getitem__(self, index):
|
||||||
|
if isinstance(index, str):
|
||||||
|
return ValueSet.from_sets(
|
||||||
|
name.infer()
|
||||||
|
for filter in self._definition_class.get_filters(is_instance=True)
|
||||||
|
for name in filter.get(index)
|
||||||
|
)
|
||||||
|
return NO_VALUES
|
||||||
|
|
||||||
|
def get_key_values(self):
|
||||||
|
filtered_values = itertools.chain.from_iterable((
|
||||||
|
f.values()
|
||||||
|
for f in self._definition_class.get_filters(is_instance=True)
|
||||||
|
))
|
||||||
|
return ValueSet({
|
||||||
|
create_simple_object(self.inference_state, v.string_name)
|
||||||
|
for v in filtered_values
|
||||||
|
})
|
||||||
|
|
||||||
|
def _get_wrapped_value(self):
|
||||||
|
d, = self.inference_state.builtins_module.py__getattribute__('dict')
|
||||||
|
result, = d.execute_with_values()
|
||||||
|
return result
|
||||||
@@ -1,29 +1,34 @@
|
|||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module
|
from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module
|
||||||
|
|
||||||
|
|
||||||
def load_proper_stub_module(inference_state, file_io, import_names, module_node):
|
def load_proper_stub_module(inference_state, grammar, file_io, import_names, module_node):
|
||||||
"""
|
"""
|
||||||
This function is given a random .pyi file and should return the proper
|
This function is given a random .pyi file and should return the proper
|
||||||
module.
|
module.
|
||||||
"""
|
"""
|
||||||
path = file_io.path
|
path = file_io.path
|
||||||
assert path.endswith('.pyi')
|
path = Path(path)
|
||||||
if path.startswith(TYPESHED_PATH):
|
assert path.suffix == '.pyi'
|
||||||
# /foo/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__
|
try:
|
||||||
rest = path[len(TYPESHED_PATH) + 1: -4]
|
relative_path = path.relative_to(TYPESHED_PATH)
|
||||||
split_paths = tuple(rest.split(os.path.sep))
|
except ValueError:
|
||||||
# Remove the stdlib/3 or third_party/3.5 part
|
pass
|
||||||
import_names = split_paths[2:]
|
else:
|
||||||
if import_names[-1] == '__init__':
|
# /[...]/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__
|
||||||
|
rest = relative_path.with_suffix('')
|
||||||
|
# Remove the stdlib/3 or third_party/3.6 part
|
||||||
|
import_names = rest.parts[2:]
|
||||||
|
if rest.name == '__init__':
|
||||||
import_names = import_names[:-1]
|
import_names = import_names[:-1]
|
||||||
|
|
||||||
if import_names is not None:
|
if import_names is not None:
|
||||||
actual_value_set = inference_state.import_module(import_names, prefer_stubs=False)
|
actual_value_set = inference_state.import_module(import_names, prefer_stubs=False)
|
||||||
|
|
||||||
stub = create_stub_module(
|
stub = create_stub_module(
|
||||||
inference_state, actual_value_set, module_node, file_io, import_names
|
inference_state, grammar, actual_value_set,
|
||||||
|
module_node, file_io, import_names
|
||||||
)
|
)
|
||||||
inference_state.stub_module_cache[import_names] = stub
|
inference_state.stub_module_cache[import_names] = stub
|
||||||
return stub
|
return stub
|
||||||
|
|||||||
@@ -7,19 +7,17 @@ from contextlib import contextmanager
|
|||||||
|
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
|
|
||||||
from jedi._compatibility import unicode
|
|
||||||
from jedi.parser_utils import get_parent_scope
|
|
||||||
|
|
||||||
|
|
||||||
def is_stdlib_path(path):
|
def is_stdlib_path(path):
|
||||||
# Python standard library paths look like this:
|
# Python standard library paths look like this:
|
||||||
# /usr/lib/python3.5/...
|
# /usr/lib/python3.9/...
|
||||||
# TODO The implementation below is probably incorrect and not complete.
|
# TODO The implementation below is probably incorrect and not complete.
|
||||||
if 'dist-packages' in path or 'site-packages' in path:
|
parts = path.parts
|
||||||
|
if 'dist-packages' in parts or 'site-packages' in parts:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
base_path = os.path.join(sys.prefix, 'lib', 'python')
|
base_path = os.path.join(sys.prefix, 'lib', 'python')
|
||||||
return bool(re.match(re.escape(base_path) + r'\d.\d', path))
|
return bool(re.match(re.escape(base_path) + r'\d.\d', str(path)))
|
||||||
|
|
||||||
|
|
||||||
def deep_ast_copy(obj):
|
def deep_ast_copy(obj):
|
||||||
@@ -122,35 +120,8 @@ def get_names_of_node(node):
|
|||||||
return list(chain.from_iterable(get_names_of_node(c) for c in children))
|
return list(chain.from_iterable(get_names_of_node(c) for c in children))
|
||||||
|
|
||||||
|
|
||||||
def get_module_names(module, all_scopes):
|
|
||||||
"""
|
|
||||||
Returns a dictionary with name parts as keys and their call paths as
|
|
||||||
values.
|
|
||||||
"""
|
|
||||||
names = list(chain.from_iterable(module.get_used_names().values()))
|
|
||||||
if not all_scopes:
|
|
||||||
# We have to filter all the names that don't have the module as a
|
|
||||||
# parent_scope. There's None as a parent, because nodes in the module
|
|
||||||
# node have the parent module and not suite as all the others.
|
|
||||||
# Therefore it's important to catch that case.
|
|
||||||
|
|
||||||
def is_module_scope_name(name):
|
|
||||||
parent_scope = get_parent_scope(name)
|
|
||||||
# async functions have an extra wrapper. Strip it.
|
|
||||||
if parent_scope and parent_scope.type == 'async_stmt':
|
|
||||||
parent_scope = parent_scope.parent
|
|
||||||
return parent_scope in (module, None)
|
|
||||||
|
|
||||||
names = [n for n in names if is_module_scope_name(n)]
|
|
||||||
return names
|
|
||||||
|
|
||||||
|
|
||||||
def is_string(value):
|
def is_string(value):
|
||||||
if value.inference_state.environment.version_info.major == 2:
|
return value.is_compiled() and isinstance(value.get_safe_value(default=None), str)
|
||||||
str_classes = (unicode, bytes)
|
|
||||||
else:
|
|
||||||
str_classes = (unicode,)
|
|
||||||
return value.is_compiled() and isinstance(value.get_safe_value(default=None), str_classes)
|
|
||||||
|
|
||||||
|
|
||||||
def is_literal(value):
|
def is_literal(value):
|
||||||
@@ -168,7 +139,7 @@ def get_int_or_none(value):
|
|||||||
|
|
||||||
|
|
||||||
def get_str_or_none(value):
|
def get_str_or_none(value):
|
||||||
return _get_safe_value_or_none(value, (bytes, unicode))
|
return _get_safe_value_or_none(value, str)
|
||||||
|
|
||||||
|
|
||||||
def is_number(value):
|
def is_number(value):
|
||||||
|
|||||||
+125
-50
@@ -5,20 +5,18 @@ not any actual importing done. This module is about finding modules in the
|
|||||||
filesystem. This can be quite tricky sometimes, because Python imports are not
|
filesystem. This can be quite tricky sometimes, because Python imports are not
|
||||||
always that simple.
|
always that simple.
|
||||||
|
|
||||||
This module uses imp for python up to 3.2 and importlib for python 3.3 on; the
|
|
||||||
correct implementation is delegated to _compatibility.
|
|
||||||
|
|
||||||
This module also supports import autocompletion, which means to complete
|
This module also supports import autocompletion, which means to complete
|
||||||
statements like ``from datetim`` (cursor at the end would return ``datetime``).
|
statements like ``from datetim`` (cursor at the end would return ``datetime``).
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
from parso.tree import search_ancestor
|
from parso.tree import search_ancestor
|
||||||
|
|
||||||
from jedi._compatibility import ImplicitNSInfo, force_unicode
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
|
from jedi.file_io import FolderIO
|
||||||
from jedi.parser_utils import get_cached_code_lines
|
from jedi.parser_utils import get_cached_code_lines
|
||||||
from jedi.inference import sys_path
|
from jedi.inference import sys_path
|
||||||
from jedi.inference import helpers
|
from jedi.inference import helpers
|
||||||
@@ -28,12 +26,13 @@ from jedi.inference.utils import unite
|
|||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.names import ImportName, SubModuleName
|
from jedi.inference.names import ImportName, SubModuleName
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
from jedi.inference.base_value import ValueSet, NO_VALUES
|
||||||
from jedi.inference.gradual.typeshed import import_module_decorator
|
from jedi.inference.gradual.typeshed import import_module_decorator, \
|
||||||
from jedi.inference.value.module import iter_module_names
|
create_stub_module, parse_stub_module
|
||||||
|
from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo
|
||||||
from jedi.plugins import plugin_manager
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
class ModuleCache(object):
|
class ModuleCache:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._name_cache = {}
|
self._name_cache = {}
|
||||||
|
|
||||||
@@ -151,7 +150,7 @@ def _level_to_base_import_path(project_path, directory, level):
|
|||||||
return None, directory
|
return None, directory
|
||||||
|
|
||||||
|
|
||||||
class Importer(object):
|
class Importer:
|
||||||
def __init__(self, inference_state, import_path, module_context, level=0):
|
def __init__(self, inference_state, import_path, module_context, level=0):
|
||||||
"""
|
"""
|
||||||
An implementation similar to ``__import__``. Use `follow`
|
An implementation similar to ``__import__``. Use `follow`
|
||||||
@@ -191,25 +190,26 @@ class Importer(object):
|
|||||||
import_path = base + tuple(import_path)
|
import_path = base + tuple(import_path)
|
||||||
else:
|
else:
|
||||||
path = module_context.py__file__()
|
path = module_context.py__file__()
|
||||||
|
project_path = self._inference_state.project.path
|
||||||
import_path = list(import_path)
|
import_path = list(import_path)
|
||||||
if path is None:
|
if path is None:
|
||||||
# If no path is defined, our best guess is that the current
|
# If no path is defined, our best guess is that the current
|
||||||
# file is edited by a user on the current working
|
# file is edited by a user on the current working
|
||||||
# directory. We need to add an initial path, because it
|
# directory. We need to add an initial path, because it
|
||||||
# will get removed as the name of the current file.
|
# will get removed as the name of the current file.
|
||||||
directory = os.getcwd()
|
directory = project_path
|
||||||
else:
|
else:
|
||||||
directory = os.path.dirname(path)
|
directory = os.path.dirname(path)
|
||||||
|
|
||||||
base_import_path, base_directory = _level_to_base_import_path(
|
base_import_path, base_directory = _level_to_base_import_path(
|
||||||
self._inference_state.project._path, directory, level,
|
project_path, directory, level,
|
||||||
)
|
)
|
||||||
if base_directory is None:
|
if base_directory is None:
|
||||||
# Everything is lost, the relative import does point
|
# Everything is lost, the relative import does point
|
||||||
# somewhere out of the filesystem.
|
# somewhere out of the filesystem.
|
||||||
self._infer_possible = False
|
self._infer_possible = False
|
||||||
else:
|
else:
|
||||||
self._fixed_sys_path = [force_unicode(base_directory)]
|
self._fixed_sys_path = [base_directory]
|
||||||
|
|
||||||
if base_import_path is None:
|
if base_import_path is None:
|
||||||
if import_path:
|
if import_path:
|
||||||
@@ -238,11 +238,49 @@ class Importer(object):
|
|||||||
# inference we want to show the user as much as possible.
|
# inference we want to show the user as much as possible.
|
||||||
# See GH #1446.
|
# See GH #1446.
|
||||||
self._inference_state.get_sys_path(add_init_paths=not is_completion)
|
self._inference_state.get_sys_path(add_init_paths=not is_completion)
|
||||||
+ sys_path.check_sys_path_modifications(self._module_context)
|
+ [
|
||||||
|
str(p) for p
|
||||||
|
in sys_path.check_sys_path_modifications(self._module_context)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def follow(self):
|
def follow(self):
|
||||||
if not self.import_path or not self._infer_possible:
|
if not self.import_path:
|
||||||
|
if self._fixed_sys_path:
|
||||||
|
# This is a bit of a special case, that maybe should be
|
||||||
|
# revisited. If the project path is wrong or the user uses
|
||||||
|
# relative imports the wrong way, we might end up here, where
|
||||||
|
# the `fixed_sys_path == project.path` in that case we kind of
|
||||||
|
# use the project.path.parent directory as our path. This is
|
||||||
|
# usually not a problem, except if imports in other places are
|
||||||
|
# using the same names. Example:
|
||||||
|
#
|
||||||
|
# foo/ < #1
|
||||||
|
# - setup.py
|
||||||
|
# - foo/ < #2
|
||||||
|
# - __init__.py
|
||||||
|
# - foo.py < #3
|
||||||
|
#
|
||||||
|
# If the top foo is our project folder and somebody uses
|
||||||
|
# `from . import foo` in `setup.py`, it will resolve to foo #2,
|
||||||
|
# which means that the import for foo.foo is cached as
|
||||||
|
# `__init__.py` (#2) and not as `foo.py` (#3). This is usually
|
||||||
|
# not an issue, because this case is probably pretty rare, but
|
||||||
|
# might be an issue for some people.
|
||||||
|
#
|
||||||
|
# However for most normal cases where we work with different
|
||||||
|
# file names, this code path hits where we basically change the
|
||||||
|
# project path to an ancestor of project path.
|
||||||
|
from jedi.inference.value.namespace import ImplicitNamespaceValue
|
||||||
|
import_path = (os.path.basename(self._fixed_sys_path[0]),)
|
||||||
|
ns = ImplicitNamespaceValue(
|
||||||
|
self._inference_state,
|
||||||
|
string_names=import_path,
|
||||||
|
paths=self._fixed_sys_path,
|
||||||
|
)
|
||||||
|
return ValueSet({ns})
|
||||||
|
return NO_VALUES
|
||||||
|
if not self._infer_possible:
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|
||||||
# Check caches first
|
# Check caches first
|
||||||
@@ -264,24 +302,15 @@ class Importer(object):
|
|||||||
Get the names of all modules in the search_path. This means file names
|
Get the names of all modules in the search_path. This means file names
|
||||||
and not names defined in the files.
|
and not names defined in the files.
|
||||||
"""
|
"""
|
||||||
names = []
|
|
||||||
# add builtin module names
|
|
||||||
if search_path is None and in_module is None:
|
|
||||||
names += [
|
|
||||||
ImportName(self._module_context, name)
|
|
||||||
for name in self._inference_state.compiled_subprocess.get_builtin_module_names()
|
|
||||||
]
|
|
||||||
|
|
||||||
if search_path is None:
|
if search_path is None:
|
||||||
search_path = self._sys_path_with_modifications(is_completion=True)
|
sys_path = self._sys_path_with_modifications(is_completion=True)
|
||||||
|
else:
|
||||||
for name in iter_module_names(self._inference_state, search_path):
|
sys_path = search_path
|
||||||
if in_module is None:
|
return list(iter_module_names(
|
||||||
n = ImportName(self._module_context, name)
|
self._inference_state, self._module_context, sys_path,
|
||||||
else:
|
module_cls=ImportName if in_module is None else SubModuleName,
|
||||||
n = SubModuleName(in_module.as_context(), name)
|
add_builtin_modules=search_path is None and in_module is None,
|
||||||
names.append(n)
|
))
|
||||||
return names
|
|
||||||
|
|
||||||
def completion_names(self, inference_state, only_modules=False):
|
def completion_names(self, inference_state, only_modules=False):
|
||||||
"""
|
"""
|
||||||
@@ -310,7 +339,7 @@ class Importer(object):
|
|||||||
values = self.follow()
|
values = self.follow()
|
||||||
for value in values:
|
for value in values:
|
||||||
# Non-modules are not completable.
|
# Non-modules are not completable.
|
||||||
if value.api_type != 'module': # not a module
|
if value.api_type not in ('module', 'namespace'): # not a module
|
||||||
continue
|
continue
|
||||||
if not value.is_compiled():
|
if not value.is_compiled():
|
||||||
# sub_modules_dict is not implemented for compiled modules.
|
# sub_modules_dict is not implemented for compiled modules.
|
||||||
@@ -339,7 +368,7 @@ def import_module_by_names(inference_state, import_names, sys_path=None,
|
|||||||
sys_path = inference_state.get_sys_path()
|
sys_path = inference_state.get_sys_path()
|
||||||
|
|
||||||
str_import_names = tuple(
|
str_import_names = tuple(
|
||||||
force_unicode(i.value if isinstance(i, tree.Name) else i)
|
i.value if isinstance(i, tree.Name) else i
|
||||||
for i in import_names
|
for i in import_names
|
||||||
)
|
)
|
||||||
value_set = [None]
|
value_set = [None]
|
||||||
@@ -440,7 +469,7 @@ def _load_python_module(inference_state, file_io,
|
|||||||
file_io=file_io,
|
file_io=file_io,
|
||||||
cache=True,
|
cache=True,
|
||||||
diff_cache=settings.fast_parser,
|
diff_cache=settings.fast_parser,
|
||||||
cache_path=settings.cache_directory
|
cache_path=settings.cache_directory,
|
||||||
)
|
)
|
||||||
|
|
||||||
from jedi.inference.value import ModuleValue
|
from jedi.inference.value import ModuleValue
|
||||||
@@ -454,8 +483,12 @@ def _load_python_module(inference_state, file_io,
|
|||||||
|
|
||||||
|
|
||||||
def _load_builtin_module(inference_state, import_names=None, sys_path=None):
|
def _load_builtin_module(inference_state, import_names=None, sys_path=None):
|
||||||
|
project = inference_state.project
|
||||||
if sys_path is None:
|
if sys_path is None:
|
||||||
sys_path = inference_state.get_sys_path()
|
sys_path = inference_state.get_sys_path()
|
||||||
|
if not project._load_unsafe_extensions:
|
||||||
|
safe_paths = project._get_base_sys_path(inference_state)
|
||||||
|
sys_path = [p for p in sys_path if p in safe_paths]
|
||||||
|
|
||||||
dotted_name = '.'.join(import_names)
|
dotted_name = '.'.join(import_names)
|
||||||
assert dotted_name is not None
|
assert dotted_name is not None
|
||||||
@@ -467,32 +500,59 @@ def _load_builtin_module(inference_state, import_names=None, sys_path=None):
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def load_module_from_path(inference_state, file_io, base_names=None):
|
def load_module_from_path(inference_state, file_io, import_names=None, is_package=None):
|
||||||
"""
|
"""
|
||||||
This should pretty much only be used for get_modules_containing_name. It's
|
This should pretty much only be used for get_modules_containing_name. It's
|
||||||
here to ensure that a random path is still properly loaded into the Jedi
|
here to ensure that a random path is still properly loaded into the Jedi
|
||||||
module structure.
|
module structure.
|
||||||
"""
|
"""
|
||||||
path = file_io.path
|
path = Path(file_io.path)
|
||||||
if base_names:
|
if import_names is None:
|
||||||
module_name = os.path.basename(path)
|
|
||||||
module_name = sys_path.remove_python_path_suffix(module_name)
|
|
||||||
is_package = module_name == '__init__'
|
|
||||||
if is_package:
|
|
||||||
import_names = base_names
|
|
||||||
else:
|
|
||||||
import_names = base_names + (module_name,)
|
|
||||||
else:
|
|
||||||
e_sys_path = inference_state.get_sys_path()
|
e_sys_path = inference_state.get_sys_path()
|
||||||
import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
|
import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
|
||||||
|
else:
|
||||||
|
assert isinstance(is_package, bool)
|
||||||
|
|
||||||
module = _load_python_module(
|
is_stub = path.suffix == '.pyi'
|
||||||
inference_state, file_io,
|
if is_stub:
|
||||||
import_names=import_names,
|
folder_io = file_io.get_parent_folder()
|
||||||
is_package=is_package,
|
if folder_io.path.endswith('-stubs'):
|
||||||
|
folder_io = FolderIO(folder_io.path[:-6])
|
||||||
|
if path.name == '__init__.pyi':
|
||||||
|
python_file_io = folder_io.get_file_io('__init__.py')
|
||||||
|
else:
|
||||||
|
python_file_io = folder_io.get_file_io(import_names[-1] + '.py')
|
||||||
|
|
||||||
|
try:
|
||||||
|
v = load_module_from_path(
|
||||||
|
inference_state, python_file_io,
|
||||||
|
import_names, is_package=is_package
|
||||||
|
)
|
||||||
|
values = ValueSet([v])
|
||||||
|
except FileNotFoundError:
|
||||||
|
values = NO_VALUES
|
||||||
|
|
||||||
|
return create_stub_module(
|
||||||
|
inference_state, inference_state.latest_grammar, values,
|
||||||
|
parse_stub_module(inference_state, file_io), file_io, import_names
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
module = _load_python_module(
|
||||||
|
inference_state, file_io,
|
||||||
|
import_names=import_names,
|
||||||
|
is_package=is_package,
|
||||||
|
)
|
||||||
|
inference_state.module_cache.add(import_names, ValueSet([module]))
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def load_namespace_from_path(inference_state, folder_io):
|
||||||
|
import_names, is_package = sys_path.transform_path_to_dotted(
|
||||||
|
inference_state.get_sys_path(),
|
||||||
|
Path(folder_io.path)
|
||||||
)
|
)
|
||||||
inference_state.module_cache.add(import_names, ValueSet([module]))
|
from jedi.inference.value.namespace import ImplicitNamespaceValue
|
||||||
return module
|
return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path])
|
||||||
|
|
||||||
|
|
||||||
def follow_error_node_imports_if_possible(context, name):
|
def follow_error_node_imports_if_possible(context, name):
|
||||||
@@ -522,3 +582,18 @@ def follow_error_node_imports_if_possible(context, name):
|
|||||||
return Importer(
|
return Importer(
|
||||||
context.inference_state, names, context.get_root_context(), level).follow()
|
context.inference_state, names, context.get_root_context(), level).follow()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def iter_module_names(inference_state, module_context, search_path,
|
||||||
|
module_cls=ImportName, add_builtin_modules=True):
|
||||||
|
"""
|
||||||
|
Get the names of all modules in the search_path. This means file names
|
||||||
|
and not names defined in the files.
|
||||||
|
"""
|
||||||
|
# add builtin module names
|
||||||
|
if add_builtin_modules:
|
||||||
|
for name in inference_state.compiled_subprocess.get_builtin_module_names():
|
||||||
|
yield module_cls(module_context, name)
|
||||||
|
|
||||||
|
for name in inference_state.compiled_subprocess.iter_module_names(search_path):
|
||||||
|
yield module_cls(module_context, name)
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
from jedi.inference.base_value import ValueSet, NO_VALUES
|
||||||
from jedi.common.utils import monkeypatch
|
from jedi.common import monkeypatch
|
||||||
|
|
||||||
|
|
||||||
class AbstractLazyValue(object):
|
class AbstractLazyValue:
|
||||||
def __init__(self, data):
|
def __init__(self, data, min=1, max=1):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.data)
|
return '<%s: %s>' % (self.__class__.__name__, self.data)
|
||||||
@@ -26,16 +28,16 @@ class LazyKnownValues(AbstractLazyValue):
|
|||||||
|
|
||||||
|
|
||||||
class LazyUnknownValue(AbstractLazyValue):
|
class LazyUnknownValue(AbstractLazyValue):
|
||||||
def __init__(self):
|
def __init__(self, min=1, max=1):
|
||||||
super(LazyUnknownValue, self).__init__(None)
|
super().__init__(None, min, max)
|
||||||
|
|
||||||
def infer(self):
|
def infer(self):
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|
||||||
|
|
||||||
class LazyTreeValue(AbstractLazyValue):
|
class LazyTreeValue(AbstractLazyValue):
|
||||||
def __init__(self, context, node):
|
def __init__(self, context, node, min=1, max=1):
|
||||||
super(LazyTreeValue, self).__init__(node)
|
super().__init__(node, min, max)
|
||||||
self.context = context
|
self.context = context
|
||||||
# We need to save the predefined names. It's an unfortunate side effect
|
# We need to save the predefined names. It's an unfortunate side effect
|
||||||
# that needs to be tracked otherwise results will be wrong.
|
# that needs to be tracked otherwise results will be wrong.
|
||||||
|
|||||||
+28
-20
@@ -1,11 +1,13 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from inspect import Parameter
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from parso.tree import search_ancestor
|
from parso.tree import search_ancestor
|
||||||
|
|
||||||
from jedi._compatibility import Parameter
|
|
||||||
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
|
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
|
||||||
from jedi.inference.utils import unite
|
from jedi.inference.utils import unite
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
from jedi.inference.base_value import ValueSet, NO_VALUES
|
||||||
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference import docstrings
|
from jedi.inference import docstrings
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
|
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
|
||||||
@@ -23,9 +25,9 @@ def _merge_name_docs(names):
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
class AbstractNameDefinition(object):
|
class AbstractNameDefinition:
|
||||||
start_pos = None
|
start_pos: Optional[Tuple[int, int]] = None
|
||||||
string_name = None
|
string_name: str
|
||||||
parent_context = None
|
parent_context = None
|
||||||
tree_name = None
|
tree_name = None
|
||||||
is_value_name = True
|
is_value_name = True
|
||||||
@@ -123,7 +125,7 @@ class AbstractTreeName(AbstractNameDefinition):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return super(AbstractTreeName, self).get_qualified_names(include_module_names)
|
return super().get_qualified_names(include_module_names)
|
||||||
|
|
||||||
def _get_qualified_names(self):
|
def _get_qualified_names(self):
|
||||||
parent_names = self.parent_context.get_qualified_names()
|
parent_names = self.parent_context.get_qualified_names()
|
||||||
@@ -223,7 +225,7 @@ class AbstractTreeName(AbstractNameDefinition):
|
|||||||
return self.tree_name.start_pos
|
return self.tree_name.start_pos
|
||||||
|
|
||||||
|
|
||||||
class ValueNameMixin(object):
|
class ValueNameMixin:
|
||||||
def infer(self):
|
def infer(self):
|
||||||
return ValueSet([self._value])
|
return ValueSet([self._value])
|
||||||
|
|
||||||
@@ -242,7 +244,7 @@ class ValueNameMixin(object):
|
|||||||
def get_root_context(self):
|
def get_root_context(self):
|
||||||
if self.parent_context is None: # A module
|
if self.parent_context is None: # A module
|
||||||
return self._value.as_context()
|
return self._value.as_context()
|
||||||
return super(ValueNameMixin, self).get_root_context()
|
return super().get_root_context()
|
||||||
|
|
||||||
def get_defining_qualified_value(self):
|
def get_defining_qualified_value(self):
|
||||||
context = self.parent_context
|
context = self.parent_context
|
||||||
@@ -257,7 +259,7 @@ class ValueNameMixin(object):
|
|||||||
|
|
||||||
class ValueName(ValueNameMixin, AbstractTreeName):
|
class ValueName(ValueNameMixin, AbstractTreeName):
|
||||||
def __init__(self, value, tree_name):
|
def __init__(self, value, tree_name):
|
||||||
super(ValueName, self).__init__(value.parent_context, tree_name)
|
super().__init__(value.parent_context, tree_name)
|
||||||
self._value = value
|
self._value = value
|
||||||
|
|
||||||
def goto(self):
|
def goto(self):
|
||||||
@@ -330,9 +332,15 @@ class TreeNameDefinition(AbstractTreeName):
|
|||||||
node = node.parent
|
node = node.parent
|
||||||
return indexes
|
return indexes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inference_state(self):
|
||||||
|
# Used by the cache function below
|
||||||
|
return self.parent_context.inference_state
|
||||||
|
|
||||||
|
@inference_state_method_cache(default='')
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
api_type = self.api_type
|
api_type = self.api_type
|
||||||
if api_type in ('function', 'class'):
|
if api_type in ('function', 'class', 'property'):
|
||||||
# Make sure the names are not TreeNameDefinitions anymore.
|
# Make sure the names are not TreeNameDefinitions anymore.
|
||||||
return clean_scope_docstring(self.tree_name.get_definition())
|
return clean_scope_docstring(self.tree_name.get_definition())
|
||||||
|
|
||||||
@@ -346,7 +354,7 @@ class TreeNameDefinition(AbstractTreeName):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class _ParamMixin(object):
|
class _ParamMixin:
|
||||||
def maybe_positional_argument(self, include_star=True):
|
def maybe_positional_argument(self, include_star=True):
|
||||||
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||||
if include_star:
|
if include_star:
|
||||||
@@ -372,7 +380,7 @@ class _ParamMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
class ParamNameInterface(_ParamMixin):
|
class ParamNameInterface(_ParamMixin):
|
||||||
api_type = u'param'
|
api_type = 'param'
|
||||||
|
|
||||||
def get_kind(self):
|
def get_kind(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -429,7 +437,7 @@ class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
|
|||||||
|
|
||||||
class _ActualTreeParamName(BaseTreeParamName):
|
class _ActualTreeParamName(BaseTreeParamName):
|
||||||
def __init__(self, function_value, tree_name):
|
def __init__(self, function_value, tree_name):
|
||||||
super(_ActualTreeParamName, self).__init__(
|
super().__init__(
|
||||||
function_value.get_default_param_context(), tree_name)
|
function_value.get_default_param_context(), tree_name)
|
||||||
self.function_value = function_value
|
self.function_value = function_value
|
||||||
|
|
||||||
@@ -499,11 +507,11 @@ class _ActualTreeParamName(BaseTreeParamName):
|
|||||||
class AnonymousParamName(_ActualTreeParamName):
|
class AnonymousParamName(_ActualTreeParamName):
|
||||||
@plugin_manager.decorate(name='goto_anonymous_param')
|
@plugin_manager.decorate(name='goto_anonymous_param')
|
||||||
def goto(self):
|
def goto(self):
|
||||||
return super(AnonymousParamName, self).goto()
|
return super().goto()
|
||||||
|
|
||||||
@plugin_manager.decorate(name='infer_anonymous_param')
|
@plugin_manager.decorate(name='infer_anonymous_param')
|
||||||
def infer(self):
|
def infer(self):
|
||||||
values = super(AnonymousParamName, self).infer()
|
values = super().infer()
|
||||||
if values:
|
if values:
|
||||||
return values
|
return values
|
||||||
from jedi.inference.dynamic_params import dynamic_param_lookup
|
from jedi.inference.dynamic_params import dynamic_param_lookup
|
||||||
@@ -527,11 +535,11 @@ class AnonymousParamName(_ActualTreeParamName):
|
|||||||
|
|
||||||
class ParamName(_ActualTreeParamName):
|
class ParamName(_ActualTreeParamName):
|
||||||
def __init__(self, function_value, tree_name, arguments):
|
def __init__(self, function_value, tree_name, arguments):
|
||||||
super(ParamName, self).__init__(function_value, tree_name)
|
super().__init__(function_value, tree_name)
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
|
|
||||||
def infer(self):
|
def infer(self):
|
||||||
values = super(ParamName, self).infer()
|
values = super().infer()
|
||||||
if values:
|
if values:
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@@ -604,7 +612,7 @@ class SubModuleName(ImportName):
|
|||||||
_level = 1
|
_level = 1
|
||||||
|
|
||||||
|
|
||||||
class NameWrapper(object):
|
class NameWrapper:
|
||||||
def __init__(self, wrapped_name):
|
def __init__(self, wrapped_name):
|
||||||
self._wrapped_name = wrapped_name
|
self._wrapped_name = wrapped_name
|
||||||
|
|
||||||
@@ -615,7 +623,7 @@ class NameWrapper(object):
|
|||||||
return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)
|
return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)
|
||||||
|
|
||||||
|
|
||||||
class StubNameMixin(object):
|
class StubNameMixin:
|
||||||
def py__doc__(self):
|
def py__doc__(self):
|
||||||
from jedi.inference.gradual.conversion import convert_names
|
from jedi.inference.gradual.conversion import convert_names
|
||||||
# Stubs are not complicated and we can just follow simple statements
|
# Stubs are not complicated and we can just follow simple statements
|
||||||
@@ -627,7 +635,7 @@ class StubNameMixin(object):
|
|||||||
|
|
||||||
names = convert_names(names, prefer_stub_to_compiled=False)
|
names = convert_names(names, prefer_stub_to_compiled=False)
|
||||||
if self in names:
|
if self in names:
|
||||||
return super(StubNameMixin, self).py__doc__()
|
return super().py__doc__()
|
||||||
else:
|
else:
|
||||||
# We have signatures ourselves in stubs, so don't use signatures
|
# We have signatures ourselves in stubs, so don't use signatures
|
||||||
# from the implementation.
|
# from the implementation.
|
||||||
@@ -637,7 +645,7 @@ class StubNameMixin(object):
|
|||||||
# From here on down we make looking up the sys.version_info fast.
|
# From here on down we make looking up the sys.version_info fast.
|
||||||
class StubName(StubNameMixin, TreeNameDefinition):
|
class StubName(StubNameMixin, TreeNameDefinition):
|
||||||
def infer(self):
|
def infer(self):
|
||||||
inferred = super(StubName, self).infer()
|
inferred = super().infer()
|
||||||
if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
|
if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
|
||||||
from jedi.inference.gradual.stub_value import VersionInfo
|
from jedi.inference.gradual.stub_value import VersionInfo
|
||||||
return ValueSet(VersionInfo(c) for c in inferred)
|
return ValueSet(VersionInfo(c) for c in inferred)
|
||||||
|
|||||||
+38
-3
@@ -1,4 +1,5 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from inspect import Parameter
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.inference.utils import PushBackIterator
|
from jedi.inference.utils import PushBackIterator
|
||||||
@@ -6,7 +7,6 @@ from jedi.inference import analysis
|
|||||||
from jedi.inference.lazy_value import LazyKnownValue, \
|
from jedi.inference.lazy_value import LazyKnownValue, \
|
||||||
LazyTreeValue, LazyUnknownValue
|
LazyTreeValue, LazyUnknownValue
|
||||||
from jedi.inference.value import iterable
|
from jedi.inference.value import iterable
|
||||||
from jedi._compatibility import Parameter
|
|
||||||
from jedi.inference.names import ParamName
|
from jedi.inference.names import ParamName
|
||||||
|
|
||||||
|
|
||||||
@@ -20,8 +20,7 @@ def _add_argument_issue(error_name, lazy_value, message):
|
|||||||
|
|
||||||
class ExecutedParamName(ParamName):
|
class ExecutedParamName(ParamName):
|
||||||
def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False):
|
def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False):
|
||||||
super(ExecutedParamName, self).__init__(
|
super().__init__(function_value, param_node.name, arguments=arguments)
|
||||||
function_value, param_node.name, arguments=arguments)
|
|
||||||
self._lazy_value = lazy_value
|
self._lazy_value = lazy_value
|
||||||
self._is_default = is_default
|
self._is_default = is_default
|
||||||
|
|
||||||
@@ -51,6 +50,25 @@ class ExecutedParamName(ParamName):
|
|||||||
|
|
||||||
|
|
||||||
def get_executed_param_names_and_issues(function_value, arguments):
|
def get_executed_param_names_and_issues(function_value, arguments):
|
||||||
|
"""
|
||||||
|
Return a tuple of:
|
||||||
|
- a list of `ExecutedParamName`s corresponding to the arguments of the
|
||||||
|
function execution `function_value`, containing the inferred value of
|
||||||
|
those arguments (whether explicit or default)
|
||||||
|
- a list of the issues encountered while building that list
|
||||||
|
|
||||||
|
For example, given:
|
||||||
|
```
|
||||||
|
def foo(a, b, c=None, d='d'): ...
|
||||||
|
|
||||||
|
foo(42, c='c')
|
||||||
|
```
|
||||||
|
|
||||||
|
Then for the execution of `foo`, this will return a tuple containing:
|
||||||
|
- a list with entries for each parameter a, b, c & d; the entries for a,
|
||||||
|
c, & d will have their values (42, 'c' and 'd' respectively) included.
|
||||||
|
- a list with a single entry about the lack of a value for `b`
|
||||||
|
"""
|
||||||
def too_many_args(argument):
|
def too_many_args(argument):
|
||||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||||
# Just report an error for the first param that is not needed (like
|
# Just report an error for the first param that is not needed (like
|
||||||
@@ -207,6 +225,23 @@ def get_executed_param_names_and_issues(function_value, arguments):
|
|||||||
|
|
||||||
|
|
||||||
def get_executed_param_names(function_value, arguments):
|
def get_executed_param_names(function_value, arguments):
|
||||||
|
"""
|
||||||
|
Return a list of `ExecutedParamName`s corresponding to the arguments of the
|
||||||
|
function execution `function_value`, containing the inferred value of those
|
||||||
|
arguments (whether explicit or default). Any issues building this list (for
|
||||||
|
example required arguments which are missing in the invocation) are ignored.
|
||||||
|
|
||||||
|
For example, given:
|
||||||
|
```
|
||||||
|
def foo(a, b, c=None, d='d'): ...
|
||||||
|
|
||||||
|
foo(42, c='c')
|
||||||
|
```
|
||||||
|
|
||||||
|
Then for the execution of `foo`, this will return a list containing entries
|
||||||
|
for each parameter a, b, c & d; the entries for a, c, & d will have their
|
||||||
|
values (42, 'c' and 'd' respectively) included.
|
||||||
|
"""
|
||||||
return get_executed_param_names_and_issues(function_value, arguments)[0]
|
return get_executed_param_names_and_issues(function_value, arguments)[0]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ Recursions are the recipe of |jedi| to conquer Python code. However, someone
|
|||||||
must stop recursions going mad. Some settings are here to make |jedi| stop at
|
must stop recursions going mad. Some settings are here to make |jedi| stop at
|
||||||
the right time. You can read more about them :ref:`here <settings-recursion>`.
|
the right time. You can read more about them :ref:`here <settings-recursion>`.
|
||||||
|
|
||||||
Next to :mod:`jedi.inference.cache` this module also makes |jedi| not
|
Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not
|
||||||
thread-safe. Why? ``execution_recursion_decorator`` uses class variables to
|
thread-safe, because ``execution_recursion_decorator`` uses class variables to
|
||||||
count the function calls.
|
count the function calls.
|
||||||
|
|
||||||
.. _settings-recursion:
|
.. _settings-recursion:
|
||||||
@@ -34,7 +34,7 @@ from jedi.inference.base_value import NO_VALUES
|
|||||||
|
|
||||||
recursion_limit = 15
|
recursion_limit = 15
|
||||||
"""
|
"""
|
||||||
Like ``sys.getrecursionlimit()``, just for |jedi|.
|
Like :func:`sys.getrecursionlimit()`, just for |jedi|.
|
||||||
"""
|
"""
|
||||||
total_function_execution_limit = 200
|
total_function_execution_limit = 200
|
||||||
"""
|
"""
|
||||||
@@ -50,7 +50,7 @@ A function may not be executed more than this number of times recursively.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class RecursionDetector(object):
|
class RecursionDetector:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pushed_nodes = []
|
self.pushed_nodes = []
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ def execution_recursion_decorator(default=NO_VALUES):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class ExecutionRecursionDetector(object):
|
class ExecutionRecursionDetector:
|
||||||
"""
|
"""
|
||||||
Catches recursions of executions.
|
Catches recursions of executions.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import re
|
|||||||
|
|
||||||
from parso import python_bytes_to_unicode
|
from parso import python_bytes_to_unicode
|
||||||
|
|
||||||
from jedi.file_io import KnownContentFileIO
|
from jedi.debug import dbg
|
||||||
from jedi.inference.imports import SubModuleName, load_module_from_path
|
from jedi.file_io import KnownContentFileIO, FolderIO
|
||||||
|
from jedi.inference.names import SubModuleName
|
||||||
|
from jedi.inference.imports import load_module_from_path
|
||||||
from jedi.inference.filters import ParserTreeFilter
|
from jedi.inference.filters import ParserTreeFilter
|
||||||
from jedi.inference.gradual.conversion import convert_names
|
from jedi.inference.gradual.conversion import convert_names
|
||||||
|
|
||||||
_IGNORE_FOLDERS = ('.tox', 'venv', '__pycache__')
|
_IGNORE_FOLDERS = ('.tox', '.venv', '.mypy_cache', 'venv', '__pycache__')
|
||||||
|
|
||||||
_OPENED_FILE_LIMIT = 2000
|
_OPENED_FILE_LIMIT = 2000
|
||||||
"""
|
"""
|
||||||
@@ -37,8 +39,7 @@ def _resolve_names(definition_names, avoid_names=()):
|
|||||||
yield name
|
yield name
|
||||||
|
|
||||||
if name.api_type == 'module':
|
if name.api_type == 'module':
|
||||||
for name in _resolve_names(name.goto(), definition_names):
|
yield from _resolve_names(name.goto(), definition_names)
|
||||||
yield name
|
|
||||||
|
|
||||||
|
|
||||||
def _dictionarize(names):
|
def _dictionarize(names):
|
||||||
@@ -89,8 +90,7 @@ def _add_names_in_same_context(context, string_name):
|
|||||||
names = set(filter_.get(string_name))
|
names = set(filter_.get(string_name))
|
||||||
if not names:
|
if not names:
|
||||||
break
|
break
|
||||||
for name in names:
|
yield from names
|
||||||
yield name
|
|
||||||
ordered = sorted(names, key=lambda x: x.start_pos)
|
ordered = sorted(names, key=lambda x: x.start_pos)
|
||||||
until_position = ordered[0].start_pos
|
until_position = ordered[0].start_pos
|
||||||
|
|
||||||
@@ -108,11 +108,10 @@ def _find_global_variables(names, search_name):
|
|||||||
for global_name in method().get(search_name):
|
for global_name in method().get(search_name):
|
||||||
yield global_name
|
yield global_name
|
||||||
c = module_context.create_context(global_name.tree_name)
|
c = module_context.create_context(global_name.tree_name)
|
||||||
for name in _add_names_in_same_context(c, global_name.string_name):
|
yield from _add_names_in_same_context(c, global_name.string_name)
|
||||||
yield name
|
|
||||||
|
|
||||||
|
|
||||||
def find_references(module_context, tree_name):
|
def find_references(module_context, tree_name, only_in_module=False):
|
||||||
inf = module_context.inference_state
|
inf = module_context.inference_state
|
||||||
search_name = tree_name.value
|
search_name = tree_name.value
|
||||||
|
|
||||||
@@ -126,10 +125,14 @@ def find_references(module_context, tree_name):
|
|||||||
|
|
||||||
found_names_dct = _dictionarize(found_names)
|
found_names_dct = _dictionarize(found_names)
|
||||||
|
|
||||||
module_contexts = set(d.get_root_context() for d in found_names)
|
module_contexts = [module_context]
|
||||||
module_contexts = [module_context] + [m for m in module_contexts if m != module_context]
|
if not only_in_module:
|
||||||
|
for m in set(d.get_root_context() for d in found_names):
|
||||||
|
if m != module_context and m.tree_node is not None \
|
||||||
|
and inf.project.path in m.py__file__().parents:
|
||||||
|
module_contexts.append(m)
|
||||||
# For param no search for other modules is necessary.
|
# For param no search for other modules is necessary.
|
||||||
if any(n.api_type == 'param' for n in found_names):
|
if only_in_module or any(n.api_type == 'param' for n in found_names):
|
||||||
potential_modules = module_contexts
|
potential_modules = module_contexts
|
||||||
else:
|
else:
|
||||||
potential_modules = get_module_contexts_containing_name(
|
potential_modules = get_module_contexts_containing_name(
|
||||||
@@ -156,7 +159,10 @@ def find_references(module_context, tree_name):
|
|||||||
else:
|
else:
|
||||||
for name in new:
|
for name in new:
|
||||||
non_matching_reference_maps.setdefault(name, []).append(new)
|
non_matching_reference_maps.setdefault(name, []).append(new)
|
||||||
return found_names_dct.values()
|
result = found_names_dct.values()
|
||||||
|
if only_in_module:
|
||||||
|
return [n for n in result if n.get_root_context() == module_context]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _check_fs(inference_state, file_io, regex):
|
def _check_fs(inference_state, file_io, regex):
|
||||||
@@ -192,16 +198,17 @@ def gitignored_lines(folder_io, file_io):
|
|||||||
return ignored_paths, ignored_names
|
return ignored_paths, ignored_names
|
||||||
|
|
||||||
|
|
||||||
def _recurse_find_python_files(folder_io, except_paths):
|
def recurse_find_python_folders_and_files(folder_io, except_paths=()):
|
||||||
|
except_paths = set(except_paths)
|
||||||
for root_folder_io, folder_ios, file_ios in folder_io.walk():
|
for root_folder_io, folder_ios, file_ios in folder_io.walk():
|
||||||
# Delete folders that we don't want to iterate over.
|
# Delete folders that we don't want to iterate over.
|
||||||
for file_io in file_ios:
|
for file_io in file_ios:
|
||||||
path = file_io.path
|
path = file_io.path
|
||||||
if path.endswith('.py') or path.endswith('.pyi'):
|
if path.suffix in ('.py', '.pyi'):
|
||||||
if path not in except_paths:
|
if path not in except_paths:
|
||||||
yield file_io
|
yield None, file_io
|
||||||
|
|
||||||
if path.endswith('.gitignore'):
|
if path.name == '.gitignore':
|
||||||
ignored_paths, ignored_names = \
|
ignored_paths, ignored_names = \
|
||||||
gitignored_lines(root_folder_io, file_io)
|
gitignored_lines(root_folder_io, file_io)
|
||||||
except_paths |= ignored_paths
|
except_paths |= ignored_paths
|
||||||
@@ -212,6 +219,14 @@ def _recurse_find_python_files(folder_io, except_paths):
|
|||||||
if folder_io.path not in except_paths
|
if folder_io.path not in except_paths
|
||||||
and folder_io.get_base_name() not in _IGNORE_FOLDERS
|
and folder_io.get_base_name() not in _IGNORE_FOLDERS
|
||||||
]
|
]
|
||||||
|
for folder_io in folder_ios:
|
||||||
|
yield folder_io, None
|
||||||
|
|
||||||
|
|
||||||
|
def recurse_find_python_files(folder_io, except_paths=()):
|
||||||
|
for folder_io, file_io in recurse_find_python_folders_and_files(folder_io, except_paths):
|
||||||
|
if file_io is not None:
|
||||||
|
yield file_io
|
||||||
|
|
||||||
|
|
||||||
def _find_python_files_in_sys_path(inference_state, module_contexts):
|
def _find_python_files_in_sys_path(inference_state, module_contexts):
|
||||||
@@ -228,13 +243,18 @@ def _find_python_files_in_sys_path(inference_state, module_contexts):
|
|||||||
path = folder_io.path
|
path = folder_io.path
|
||||||
if not any(path.startswith(p) for p in sys_path) or path in except_paths:
|
if not any(path.startswith(p) for p in sys_path) or path in except_paths:
|
||||||
break
|
break
|
||||||
for file_io in _recurse_find_python_files(folder_io, except_paths):
|
for file_io in recurse_find_python_files(folder_io, except_paths):
|
||||||
if file_io.path not in yielded_paths:
|
if file_io.path not in yielded_paths:
|
||||||
yield file_io
|
yield file_io
|
||||||
except_paths.add(path)
|
except_paths.add(path)
|
||||||
folder_io = folder_io.get_parent_folder()
|
folder_io = folder_io.get_parent_folder()
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_modules(inference_state, module_contexts):
|
||||||
|
except_ = [m.py__file__() for m in module_contexts]
|
||||||
|
yield from recurse_find_python_files(FolderIO(inference_state.project.path), except_)
|
||||||
|
|
||||||
|
|
||||||
def get_module_contexts_containing_name(inference_state, module_contexts, name,
|
def get_module_contexts_containing_name(inference_state, module_contexts, name,
|
||||||
limit_reduction=1):
|
limit_reduction=1):
|
||||||
"""
|
"""
|
||||||
@@ -254,19 +274,30 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name,
|
|||||||
if len(name) <= 2:
|
if len(name) <= 2:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Currently not used, because there's only `scope=project` and `scope=file`
|
||||||
|
# At the moment there is no such thing as `scope=sys.path`.
|
||||||
|
# file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts)
|
||||||
|
file_io_iterator = _find_project_modules(inference_state, module_contexts)
|
||||||
|
yield from search_in_file_ios(inference_state, file_io_iterator, name,
|
||||||
|
limit_reduction=limit_reduction)
|
||||||
|
|
||||||
|
|
||||||
|
def search_in_file_ios(inference_state, file_io_iterator, name, limit_reduction=1):
|
||||||
parse_limit = _PARSED_FILE_LIMIT / limit_reduction
|
parse_limit = _PARSED_FILE_LIMIT / limit_reduction
|
||||||
open_limit = _OPENED_FILE_LIMIT / limit_reduction
|
open_limit = _OPENED_FILE_LIMIT / limit_reduction
|
||||||
file_io_count = 0
|
file_io_count = 0
|
||||||
parsed_file_count = 0
|
parsed_file_count = 0
|
||||||
regex = re.compile(r'\b' + re.escape(name) + r'\b')
|
regex = re.compile(r'\b' + re.escape(name) + r'\b')
|
||||||
for file_io in _find_python_files_in_sys_path(inference_state, module_contexts):
|
for file_io in file_io_iterator:
|
||||||
file_io_count += 1
|
file_io_count += 1
|
||||||
m = _check_fs(inference_state, file_io, regex)
|
m = _check_fs(inference_state, file_io, regex)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
parsed_file_count += 1
|
parsed_file_count += 1
|
||||||
yield m
|
yield m
|
||||||
if parsed_file_count >= parse_limit:
|
if parsed_file_count >= parse_limit:
|
||||||
|
dbg('Hit limit of parsed files: %s', parse_limit)
|
||||||
break
|
break
|
||||||
|
|
||||||
if file_io_count >= open_limit:
|
if file_io_count >= open_limit:
|
||||||
|
dbg('Hit limit of opened files: %s', open_limit)
|
||||||
break
|
break
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
from jedi._compatibility import Parameter
|
from inspect import Parameter
|
||||||
|
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import parser_utils
|
from jedi import parser_utils
|
||||||
|
|
||||||
|
|
||||||
class _SignatureMixin(object):
|
class _SignatureMixin:
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
def param_strings():
|
def param_strings():
|
||||||
is_positional = False
|
is_positional = False
|
||||||
@@ -67,7 +68,7 @@ class AbstractSignature(_SignatureMixin):
|
|||||||
|
|
||||||
class TreeSignature(AbstractSignature):
|
class TreeSignature(AbstractSignature):
|
||||||
def __init__(self, value, function_value=None, is_bound=False):
|
def __init__(self, value, function_value=None, is_bound=False):
|
||||||
super(TreeSignature, self).__init__(value, is_bound)
|
super().__init__(value, is_bound)
|
||||||
self._function_value = function_value or value
|
self._function_value = function_value or value
|
||||||
|
|
||||||
def bind(self, value):
|
def bind(self, value):
|
||||||
@@ -90,10 +91,12 @@ class TreeSignature(AbstractSignature):
|
|||||||
|
|
||||||
@memoize_method
|
@memoize_method
|
||||||
def get_param_names(self, resolve_stars=False):
|
def get_param_names(self, resolve_stars=False):
|
||||||
params = super(TreeSignature, self).get_param_names(resolve_stars=False)
|
params = self._function_value.get_param_names()
|
||||||
if resolve_stars:
|
if resolve_stars:
|
||||||
from jedi.inference.star_args import process_params
|
from jedi.inference.star_args import process_params
|
||||||
params = process_params(params)
|
params = process_params(params)
|
||||||
|
if self.is_bound:
|
||||||
|
return params[1:]
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def matches_signature(self, arguments):
|
def matches_signature(self, arguments):
|
||||||
@@ -119,7 +122,7 @@ class TreeSignature(AbstractSignature):
|
|||||||
|
|
||||||
class BuiltinSignature(AbstractSignature):
|
class BuiltinSignature(AbstractSignature):
|
||||||
def __init__(self, value, return_string, function_value=None, is_bound=False):
|
def __init__(self, value, return_string, function_value=None, is_bound=False):
|
||||||
super(BuiltinSignature, self).__init__(value, is_bound)
|
super().__init__(value, is_bound)
|
||||||
self._return_string = return_string
|
self._return_string = return_string
|
||||||
self.__function_value = function_value
|
self.__function_value = function_value
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ This means for example in this case::
|
|||||||
|
|
||||||
The signature here for bar should be `bar(b, c)` instead of bar(*args).
|
The signature here for bar should be `bar(b, c)` instead of bar(*args).
|
||||||
"""
|
"""
|
||||||
|
from inspect import Parameter
|
||||||
|
|
||||||
from jedi._compatibility import Parameter
|
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
from jedi.inference.names import ParamNameWrapper
|
from jedi.inference.names import ParamNameWrapper
|
||||||
from jedi.inference.helpers import is_big_annoying_library
|
from jedi.inference.helpers import is_big_annoying_library
|
||||||
@@ -32,8 +32,6 @@ def _iter_nodes_for_param(param_name):
|
|||||||
argument = name.parent
|
argument = name.parent
|
||||||
if argument.type == 'argument' \
|
if argument.type == 'argument' \
|
||||||
and argument.children[0] == '*' * param_name.star_count:
|
and argument.children[0] == '*' * param_name.star_count:
|
||||||
# No support for Python <= 3.4 here, but they are end-of-life
|
|
||||||
# anyway
|
|
||||||
trailer = search_ancestor(argument, 'trailer')
|
trailer = search_ancestor(argument, 'trailer')
|
||||||
if trailer is not None: # Make sure we're in a function
|
if trailer is not None: # Make sure we're in a function
|
||||||
context = execution_context.create_context(trailer)
|
context = execution_context.create_context(trailer)
|
||||||
@@ -98,8 +96,7 @@ def process_params(param_names, star_count=3): # default means both * and **
|
|||||||
if is_big_annoying_library(param_names[0].parent_context):
|
if is_big_annoying_library(param_names[0].parent_context):
|
||||||
# At first this feature can look innocent, but it does a lot of
|
# At first this feature can look innocent, but it does a lot of
|
||||||
# type inference in some cases, so we just ditch it.
|
# type inference in some cases, so we just ditch it.
|
||||||
for p in param_names:
|
yield from param_names
|
||||||
yield p
|
|
||||||
return
|
return
|
||||||
|
|
||||||
used_names = set()
|
used_names = set()
|
||||||
@@ -210,7 +207,7 @@ def process_params(param_names, star_count=3): # default means both * and **
|
|||||||
|
|
||||||
class ParamNameFixedKind(ParamNameWrapper):
|
class ParamNameFixedKind(ParamNameWrapper):
|
||||||
def __init__(self, param_name, new_kind):
|
def __init__(self, param_name, new_kind):
|
||||||
super(ParamNameFixedKind, self).__init__(param_name)
|
super().__init__(param_name)
|
||||||
self._new_kind = new_kind
|
self._new_kind = new_kind
|
||||||
|
|
||||||
def get_kind(self):
|
def get_kind(self):
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import copy
|
|||||||
|
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
|
|
||||||
from jedi._compatibility import force_unicode, unicode
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import parser_utils
|
from jedi import parser_utils
|
||||||
from jedi.inference.base_value import ValueSet, NO_VALUES, ContextualizedNode, \
|
from jedi.inference.base_value import ValueSet, NO_VALUES, ContextualizedNode, \
|
||||||
@@ -31,6 +30,26 @@ from jedi.inference.context import CompForContext
|
|||||||
from jedi.inference.value.decorator import Decoratee
|
from jedi.inference.value.decorator import Decoratee
|
||||||
from jedi.plugins import plugin_manager
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
operator_to_magic_method = {
|
||||||
|
'+': '__add__',
|
||||||
|
'-': '__sub__',
|
||||||
|
'*': '__mul__',
|
||||||
|
'@': '__matmul__',
|
||||||
|
'/': '__truediv__',
|
||||||
|
'//': '__floordiv__',
|
||||||
|
'%': '__mod__',
|
||||||
|
'**': '__pow__',
|
||||||
|
'<<': '__lshift__',
|
||||||
|
'>>': '__rshift__',
|
||||||
|
'&': '__and__',
|
||||||
|
'|': '__or__',
|
||||||
|
'^': '__xor__',
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_operator_to_magic_method = {
|
||||||
|
k: '__r' + v[2:] for k, v in operator_to_magic_method.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _limit_value_infers(func):
|
def _limit_value_infers(func):
|
||||||
"""
|
"""
|
||||||
@@ -205,12 +224,10 @@ def _infer_node(context, element):
|
|||||||
| context.infer_node(element.children[-1]))
|
| context.infer_node(element.children[-1]))
|
||||||
elif typ == 'operator':
|
elif typ == 'operator':
|
||||||
# Must be an ellipsis, other operators are not inferred.
|
# Must be an ellipsis, other operators are not inferred.
|
||||||
# In Python 2 ellipsis is coded as three single dot tokens, not
|
if element.value != '...':
|
||||||
# as one token 3 dot token.
|
|
||||||
if element.value not in ('.', '...'):
|
|
||||||
origin = element.parent
|
origin = element.parent
|
||||||
raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin))
|
raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin))
|
||||||
return ValueSet([compiled.builtin_from_name(inference_state, u'Ellipsis')])
|
return ValueSet([compiled.builtin_from_name(inference_state, 'Ellipsis')])
|
||||||
elif typ == 'dotted_name':
|
elif typ == 'dotted_name':
|
||||||
value_set = infer_atom(context, element.children[0])
|
value_set = infer_atom(context, element.children[0])
|
||||||
for next_name in element.children[2::2]:
|
for next_name in element.children[2::2]:
|
||||||
@@ -269,15 +286,12 @@ def infer_atom(context, atom):
|
|||||||
"""
|
"""
|
||||||
state = context.inference_state
|
state = context.inference_state
|
||||||
if atom.type == 'name':
|
if atom.type == 'name':
|
||||||
if atom.value in ('True', 'False', 'None'):
|
|
||||||
# Python 2...
|
|
||||||
return ValueSet([compiled.builtin_from_name(state, atom.value)])
|
|
||||||
|
|
||||||
# This is the first global lookup.
|
# This is the first global lookup.
|
||||||
stmt = tree.search_ancestor(
|
stmt = tree.search_ancestor(atom, 'expr_stmt', 'lambdef', 'if_stmt') or atom
|
||||||
atom, 'expr_stmt', 'lambdef'
|
if stmt.type == 'if_stmt':
|
||||||
) or atom
|
if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()):
|
||||||
if stmt.type == 'lambdef':
|
stmt = atom
|
||||||
|
elif stmt.type == 'lambdef':
|
||||||
stmt = atom
|
stmt = atom
|
||||||
position = stmt.start_pos
|
position = stmt.start_pos
|
||||||
if _is_annotation_name(atom):
|
if _is_annotation_name(atom):
|
||||||
@@ -292,9 +306,6 @@ def infer_atom(context, atom):
|
|||||||
# For False/True/None
|
# For False/True/None
|
||||||
if atom.value in ('False', 'True', 'None'):
|
if atom.value in ('False', 'True', 'None'):
|
||||||
return ValueSet([compiled.builtin_from_name(state, atom.value)])
|
return ValueSet([compiled.builtin_from_name(state, atom.value)])
|
||||||
elif atom.value == 'print':
|
|
||||||
# print e.g. could be inferred like this in Python 2.7
|
|
||||||
return NO_VALUES
|
|
||||||
elif atom.value == 'yield':
|
elif atom.value == 'yield':
|
||||||
# Contrary to yield from, yield can just appear alone to return a
|
# Contrary to yield from, yield can just appear alone to return a
|
||||||
# value when used with `.send()`.
|
# value when used with `.send()`.
|
||||||
@@ -309,7 +320,7 @@ def infer_atom(context, atom):
|
|||||||
value_set = infer_atom(context, atom.children[0])
|
value_set = infer_atom(context, atom.children[0])
|
||||||
for string in atom.children[1:]:
|
for string in atom.children[1:]:
|
||||||
right = infer_atom(context, string)
|
right = infer_atom(context, string)
|
||||||
value_set = _infer_comparison(context, value_set, u'+', right)
|
value_set = _infer_comparison(context, value_set, '+', right)
|
||||||
return value_set
|
return value_set
|
||||||
elif atom.type == 'fstring':
|
elif atom.type == 'fstring':
|
||||||
return compiled.get_string_value_set(state)
|
return compiled.get_string_value_set(state)
|
||||||
@@ -356,6 +367,12 @@ def infer_atom(context, atom):
|
|||||||
def infer_expr_stmt(context, stmt, seek_name=None):
|
def infer_expr_stmt(context, stmt, seek_name=None):
|
||||||
with recursion.execution_allowed(context.inference_state, stmt) as allowed:
|
with recursion.execution_allowed(context.inference_state, stmt) as allowed:
|
||||||
if allowed:
|
if allowed:
|
||||||
|
if seek_name is not None:
|
||||||
|
pep0484_values = \
|
||||||
|
annotation.find_type_from_comment_hint_assign(context, stmt, seek_name)
|
||||||
|
if pep0484_values:
|
||||||
|
return pep0484_values
|
||||||
|
|
||||||
return _infer_expr_stmt(context, stmt, seek_name)
|
return _infer_expr_stmt(context, stmt, seek_name)
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|
||||||
@@ -388,6 +405,7 @@ def _infer_expr_stmt(context, stmt, seek_name=None):
|
|||||||
|
|
||||||
debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name)
|
debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name)
|
||||||
rhs = stmt.get_rhs()
|
rhs = stmt.get_rhs()
|
||||||
|
|
||||||
value_set = context.infer_node(rhs)
|
value_set = context.infer_node(rhs)
|
||||||
|
|
||||||
if seek_name:
|
if seek_name:
|
||||||
@@ -531,16 +549,16 @@ def _is_annotation_name(name):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _is_tuple(value):
|
|
||||||
return isinstance(value, iterable.Sequence) and value.array_type == 'tuple'
|
|
||||||
|
|
||||||
|
|
||||||
def _is_list(value):
|
def _is_list(value):
|
||||||
return isinstance(value, iterable.Sequence) and value.array_type == 'list'
|
return value.array_type == 'list'
|
||||||
|
|
||||||
|
|
||||||
|
def _is_tuple(value):
|
||||||
|
return value.array_type == 'tuple'
|
||||||
|
|
||||||
|
|
||||||
def _bool_to_value(inference_state, bool_):
|
def _bool_to_value(inference_state, bool_):
|
||||||
return compiled.builtin_from_name(inference_state, force_unicode(str(bool_)))
|
return compiled.builtin_from_name(inference_state, str(bool_))
|
||||||
|
|
||||||
|
|
||||||
def _get_tuple_ints(value):
|
def _get_tuple_ints(value):
|
||||||
@@ -563,10 +581,10 @@ def _get_tuple_ints(value):
|
|||||||
def _infer_comparison_part(inference_state, context, left, operator, right):
|
def _infer_comparison_part(inference_state, context, left, operator, right):
|
||||||
l_is_num = is_number(left)
|
l_is_num = is_number(left)
|
||||||
r_is_num = is_number(right)
|
r_is_num = is_number(right)
|
||||||
if isinstance(operator, unicode):
|
if isinstance(operator, str):
|
||||||
str_operator = operator
|
str_operator = operator
|
||||||
else:
|
else:
|
||||||
str_operator = force_unicode(str(operator.value))
|
str_operator = str(operator.value)
|
||||||
|
|
||||||
if str_operator == '*':
|
if str_operator == '*':
|
||||||
# for iterables, ignore * operations
|
# for iterables, ignore * operations
|
||||||
@@ -577,7 +595,7 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
|
|||||||
elif str_operator == '+':
|
elif str_operator == '+':
|
||||||
if l_is_num and r_is_num or is_string(left) and is_string(right):
|
if l_is_num and r_is_num or is_string(left) and is_string(right):
|
||||||
return left.execute_operation(right, str_operator)
|
return left.execute_operation(right, str_operator)
|
||||||
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
|
elif _is_list(left) and _is_list(right) or _is_tuple(left) and _is_tuple(right):
|
||||||
return ValueSet([iterable.MergedArray(inference_state, (left, right))])
|
return ValueSet([iterable.MergedArray(inference_state, (left, right))])
|
||||||
elif str_operator == '-':
|
elif str_operator == '-':
|
||||||
if l_is_num and r_is_num:
|
if l_is_num and r_is_num:
|
||||||
@@ -596,7 +614,11 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
|
|||||||
if str_operator in ('is', '!=', '==', 'is not'):
|
if str_operator in ('is', '!=', '==', 'is not'):
|
||||||
operation = COMPARISON_OPERATORS[str_operator]
|
operation = COMPARISON_OPERATORS[str_operator]
|
||||||
bool_ = operation(left, right)
|
bool_ = operation(left, right)
|
||||||
return ValueSet([_bool_to_value(inference_state, bool_)])
|
# Only if == returns True or != returns False, we can continue.
|
||||||
|
# There's no guarantee that they are not equal. This can help
|
||||||
|
# in some cases, but does not cover everything.
|
||||||
|
if (str_operator in ('is', '==')) == bool_:
|
||||||
|
return ValueSet([_bool_to_value(inference_state, bool_)])
|
||||||
|
|
||||||
if isinstance(left, VersionInfo):
|
if isinstance(left, VersionInfo):
|
||||||
version_info = _get_tuple_ints(right)
|
version_info = _get_tuple_ints(right)
|
||||||
@@ -611,7 +633,7 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
|
|||||||
_bool_to_value(inference_state, True),
|
_bool_to_value(inference_state, True),
|
||||||
_bool_to_value(inference_state, False)
|
_bool_to_value(inference_state, False)
|
||||||
])
|
])
|
||||||
elif str_operator == 'in':
|
elif str_operator in ('in', 'not in'):
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
|
||||||
def check(obj):
|
def check(obj):
|
||||||
@@ -626,28 +648,29 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
|
|||||||
analysis.add(context, 'type-error-operation', operator,
|
analysis.add(context, 'type-error-operation', operator,
|
||||||
message % (left, right))
|
message % (left, right))
|
||||||
|
|
||||||
|
if left.is_class() or right.is_class():
|
||||||
|
return NO_VALUES
|
||||||
|
|
||||||
|
method_name = operator_to_magic_method[str_operator]
|
||||||
|
magic_methods = left.py__getattribute__(method_name)
|
||||||
|
if magic_methods:
|
||||||
|
result = magic_methods.execute_with_values(right)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
if not magic_methods:
|
||||||
|
reverse_method_name = reverse_operator_to_magic_method[str_operator]
|
||||||
|
magic_methods = right.py__getattribute__(reverse_method_name)
|
||||||
|
|
||||||
|
result = magic_methods.execute_with_values(left)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
result = ValueSet([left, right])
|
result = ValueSet([left, right])
|
||||||
debug.dbg('Used operator %s resulting in %s', operator, result)
|
debug.dbg('Used operator %s resulting in %s', operator, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _remove_statements(context, stmt, name):
|
|
||||||
"""
|
|
||||||
This is the part where statements are being stripped.
|
|
||||||
|
|
||||||
Due to lazy type inference, statements like a = func; b = a; b() have to be
|
|
||||||
inferred.
|
|
||||||
|
|
||||||
TODO merge with infer_expr_stmt?
|
|
||||||
"""
|
|
||||||
pep0484_values = \
|
|
||||||
annotation.find_type_from_comment_hint_assign(context, stmt, name)
|
|
||||||
if pep0484_values:
|
|
||||||
return pep0484_values
|
|
||||||
|
|
||||||
return infer_expr_stmt(context, stmt, seek_name=name)
|
|
||||||
|
|
||||||
|
|
||||||
@plugin_manager.decorate()
|
@plugin_manager.decorate()
|
||||||
def tree_name_to_values(inference_state, context, tree_name):
|
def tree_name_to_values(inference_state, context, tree_name):
|
||||||
value_set = NO_VALUES
|
value_set = NO_VALUES
|
||||||
@@ -655,16 +678,18 @@ def tree_name_to_values(inference_state, context, tree_name):
|
|||||||
# First check for annotations, like: `foo: int = 3`
|
# First check for annotations, like: `foo: int = 3`
|
||||||
if module_node is not None:
|
if module_node is not None:
|
||||||
names = module_node.get_used_names().get(tree_name.value, [])
|
names = module_node.get_used_names().get(tree_name.value, [])
|
||||||
|
found_annotation = False
|
||||||
for name in names:
|
for name in names:
|
||||||
expr_stmt = name.parent
|
expr_stmt = name.parent
|
||||||
|
|
||||||
if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign":
|
if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign":
|
||||||
correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
|
correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
|
||||||
if correct_scope:
|
if correct_scope:
|
||||||
|
found_annotation = True
|
||||||
value_set |= annotation.infer_annotation(
|
value_set |= annotation.infer_annotation(
|
||||||
context, expr_stmt.children[1].children[1]
|
context, expr_stmt.children[1].children[1]
|
||||||
).execute_annotation()
|
).execute_annotation()
|
||||||
if value_set:
|
if found_annotation:
|
||||||
return value_set
|
return value_set
|
||||||
|
|
||||||
types = []
|
types = []
|
||||||
@@ -710,10 +735,10 @@ def tree_name_to_values(inference_state, context, tree_name):
|
|||||||
n = TreeNameDefinition(context, tree_name)
|
n = TreeNameDefinition(context, tree_name)
|
||||||
types = check_tuple_assignments(n, for_types)
|
types = check_tuple_assignments(n, for_types)
|
||||||
elif typ == 'expr_stmt':
|
elif typ == 'expr_stmt':
|
||||||
types = _remove_statements(context, node, tree_name)
|
types = infer_expr_stmt(context, node, tree_name)
|
||||||
elif typ == 'with_stmt':
|
elif typ == 'with_stmt':
|
||||||
value_managers = context.infer_node(node.get_test_node_from_name(tree_name))
|
value_managers = context.infer_node(node.get_test_node_from_name(tree_name))
|
||||||
enter_methods = value_managers.py__getattribute__(u'__enter__')
|
enter_methods = value_managers.py__getattribute__('__enter__')
|
||||||
return enter_methods.execute_with_values()
|
return enter_methods.execute_with_values()
|
||||||
elif typ in ('import_from', 'import_name'):
|
elif typ in ('import_from', 'import_name'):
|
||||||
types = imports.infer_import(context, tree_name)
|
types = imports.infer_import(context, tree_name)
|
||||||
@@ -729,6 +754,8 @@ def tree_name_to_values(inference_state, context, tree_name):
|
|||||||
types = NO_VALUES
|
types = NO_VALUES
|
||||||
elif typ == 'del_stmt':
|
elif typ == 'del_stmt':
|
||||||
types = NO_VALUES
|
types = NO_VALUES
|
||||||
|
elif typ == 'namedexpr_test':
|
||||||
|
types = infer_node(context, node)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Should not happen. type: %s" % typ)
|
raise ValueError("Should not happen. type: %s" % typ)
|
||||||
return types
|
return types
|
||||||
@@ -797,7 +824,8 @@ def check_tuple_assignments(name, value_set):
|
|||||||
if isinstance(index, slice):
|
if isinstance(index, slice):
|
||||||
# For no star unpacking is not possible.
|
# For no star unpacking is not possible.
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
for _ in range(index + 1):
|
i = 0
|
||||||
|
while i <= index:
|
||||||
try:
|
try:
|
||||||
lazy_value = next(iterated)
|
lazy_value = next(iterated)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
@@ -806,6 +834,8 @@ def check_tuple_assignments(name, value_set):
|
|||||||
# index number is high. Therefore break if the loop is
|
# index number is high. Therefore break if the loop is
|
||||||
# finished.
|
# finished.
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
else:
|
||||||
|
i += lazy_value.max
|
||||||
value_set = lazy_value.infer()
|
value_set = lazy_value.infer()
|
||||||
return value_set
|
return value_set
|
||||||
|
|
||||||
@@ -824,8 +854,7 @@ def _infer_subscript_list(context, index):
|
|||||||
return ValueSet([iterable.Slice(context, None, None, None)])
|
return ValueSet([iterable.Slice(context, None, None, None)])
|
||||||
|
|
||||||
elif index.type == 'subscript' and not index.children[0] == '.':
|
elif index.type == 'subscript' and not index.children[0] == '.':
|
||||||
# subscript basically implies a slice operation, except for Python 2's
|
# subscript basically implies a slice operation
|
||||||
# Ellipsis.
|
|
||||||
# e.g. array[:3]
|
# e.g. array[:3]
|
||||||
result = []
|
result = []
|
||||||
for el in index.children:
|
for el in index.children:
|
||||||
|
|||||||
+35
-30
@@ -1,10 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from importlib.machinery import all_suffixes
|
||||||
|
|
||||||
from jedi._compatibility import unicode, force_unicode, all_suffixes
|
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.base_value import ContextualizedNode
|
from jedi.inference.base_value import ContextualizedNode
|
||||||
from jedi.inference.helpers import is_string, get_str_or_none
|
from jedi.inference.helpers import is_string, get_str_or_none
|
||||||
from jedi.common.utils import traverse_parents
|
|
||||||
from jedi.parser_utils import get_cached_code_lines
|
from jedi.parser_utils import get_cached_code_lines
|
||||||
from jedi.file_io import FileIO
|
from jedi.file_io import FileIO
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
@@ -13,8 +14,9 @@ from jedi import debug
|
|||||||
_BUILDOUT_PATH_INSERTION_LIMIT = 10
|
_BUILDOUT_PATH_INSERTION_LIMIT = 10
|
||||||
|
|
||||||
|
|
||||||
def _abs_path(module_context, path):
|
def _abs_path(module_context, str_path: str):
|
||||||
if os.path.isabs(path):
|
path = Path(str_path)
|
||||||
|
if path.is_absolute():
|
||||||
return path
|
return path
|
||||||
|
|
||||||
module_path = module_context.py__file__()
|
module_path = module_context.py__file__()
|
||||||
@@ -23,9 +25,8 @@ def _abs_path(module_context, path):
|
|||||||
# system.
|
# system.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
base_dir = os.path.dirname(module_path)
|
base_dir = module_path.parent
|
||||||
path = force_unicode(path)
|
return base_dir.joinpath(path).absolute()
|
||||||
return os.path.abspath(os.path.join(base_dir, path))
|
|
||||||
|
|
||||||
|
|
||||||
def _paths_from_assignment(module_context, expr_stmt):
|
def _paths_from_assignment(module_context, expr_stmt):
|
||||||
@@ -147,7 +148,7 @@ def discover_buildout_paths(inference_state, script_path):
|
|||||||
|
|
||||||
|
|
||||||
def _get_paths_from_buildout_script(inference_state, buildout_script_path):
|
def _get_paths_from_buildout_script(inference_state, buildout_script_path):
|
||||||
file_io = FileIO(buildout_script_path)
|
file_io = FileIO(str(buildout_script_path))
|
||||||
try:
|
try:
|
||||||
module_node = inference_state.parse(
|
module_node = inference_state.parse(
|
||||||
file_io=file_io,
|
file_io=file_io,
|
||||||
@@ -165,18 +166,20 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path):
|
|||||||
string_names=None,
|
string_names=None,
|
||||||
code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path),
|
code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path),
|
||||||
).as_context()
|
).as_context()
|
||||||
for path in check_sys_path_modifications(module_context):
|
yield from check_sys_path_modifications(module_context)
|
||||||
yield path
|
|
||||||
|
|
||||||
|
|
||||||
def _get_parent_dir_with_file(path, filename):
|
def _get_parent_dir_with_file(path: Path, filename):
|
||||||
for parent in traverse_parents(path):
|
for parent in path.parents:
|
||||||
if os.path.isfile(os.path.join(parent, filename)):
|
try:
|
||||||
return parent
|
if parent.joinpath(filename).is_file():
|
||||||
|
return parent
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_buildout_script_paths(search_path):
|
def _get_buildout_script_paths(search_path: Path):
|
||||||
"""
|
"""
|
||||||
if there is a 'buildout.cfg' file in one of the parent directories of the
|
if there is a 'buildout.cfg' file in one of the parent directories of the
|
||||||
given module it will return a list of all files in the buildout bin
|
given module it will return a list of all files in the buildout bin
|
||||||
@@ -188,13 +191,13 @@ def _get_buildout_script_paths(search_path):
|
|||||||
project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg')
|
project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg')
|
||||||
if not project_root:
|
if not project_root:
|
||||||
return
|
return
|
||||||
bin_path = os.path.join(project_root, 'bin')
|
bin_path = project_root.joinpath('bin')
|
||||||
if not os.path.exists(bin_path):
|
if not bin_path.exists():
|
||||||
return
|
return
|
||||||
|
|
||||||
for filename in os.listdir(bin_path):
|
for filename in os.listdir(bin_path):
|
||||||
try:
|
try:
|
||||||
filepath = os.path.join(bin_path, filename)
|
filepath = bin_path.joinpath(filename)
|
||||||
with open(filepath, 'r') as f:
|
with open(filepath, 'r') as f:
|
||||||
firstline = f.readline()
|
firstline = f.readline()
|
||||||
if firstline.startswith('#!') and 'python' in firstline:
|
if firstline.startswith('#!') and 'python' in firstline:
|
||||||
@@ -202,14 +205,14 @@ def _get_buildout_script_paths(search_path):
|
|||||||
except (UnicodeDecodeError, IOError) as e:
|
except (UnicodeDecodeError, IOError) as e:
|
||||||
# Probably a binary file; permission error or race cond. because
|
# Probably a binary file; permission error or race cond. because
|
||||||
# file got deleted. Ignore it.
|
# file got deleted. Ignore it.
|
||||||
debug.warning(unicode(e))
|
debug.warning(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
def remove_python_path_suffix(path):
|
def remove_python_path_suffix(path):
|
||||||
for suffix in all_suffixes():
|
for suffix in all_suffixes() + ['.pyi']:
|
||||||
if path.endswith(suffix):
|
if path.suffix == suffix:
|
||||||
path = path[:-len(suffix)]
|
path = path.with_name(path.stem)
|
||||||
break
|
break
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@@ -218,8 +221,7 @@ def transform_path_to_dotted(sys_path, module_path):
|
|||||||
"""
|
"""
|
||||||
Returns the dotted path inside a sys.path as a list of names. e.g.
|
Returns the dotted path inside a sys.path as a list of names. e.g.
|
||||||
|
|
||||||
>>> from os.path import abspath
|
>>> transform_path_to_dotted([str(Path("/foo").absolute())], Path('/foo/bar/baz.py').absolute())
|
||||||
>>> transform_path_to_dotted([abspath("/foo")], abspath('/foo/bar/baz.py'))
|
|
||||||
(('bar', 'baz'), False)
|
(('bar', 'baz'), False)
|
||||||
|
|
||||||
Returns (None, False) if the path doesn't really resolve to anything.
|
Returns (None, False) if the path doesn't really resolve to anything.
|
||||||
@@ -227,21 +229,22 @@ def transform_path_to_dotted(sys_path, module_path):
|
|||||||
"""
|
"""
|
||||||
# First remove the suffix.
|
# First remove the suffix.
|
||||||
module_path = remove_python_path_suffix(module_path)
|
module_path = remove_python_path_suffix(module_path)
|
||||||
|
if module_path.name.startswith('.'):
|
||||||
|
return None, False
|
||||||
|
|
||||||
# Once the suffix was removed we are using the files as we know them. This
|
# Once the suffix was removed we are using the files as we know them. This
|
||||||
# means that if someone uses an ending like .vim for a Python file, .vim
|
# means that if someone uses an ending like .vim for a Python file, .vim
|
||||||
# will be part of the returned dotted part.
|
# will be part of the returned dotted part.
|
||||||
|
|
||||||
is_package = module_path.endswith(os.path.sep + '__init__')
|
is_package = module_path.name == '__init__'
|
||||||
if is_package:
|
if is_package:
|
||||||
# -1 to remove the separator
|
module_path = module_path.parent
|
||||||
module_path = module_path[:-len('__init__') - 1]
|
|
||||||
|
|
||||||
def iter_potential_solutions():
|
def iter_potential_solutions():
|
||||||
for p in sys_path:
|
for p in sys_path:
|
||||||
if module_path.startswith(p):
|
if str(module_path).startswith(p):
|
||||||
# Strip the trailing slash/backslash
|
# Strip the trailing slash/backslash
|
||||||
rest = module_path[len(p):]
|
rest = str(module_path)[len(p):]
|
||||||
# On Windows a path can also use a slash.
|
# On Windows a path can also use a slash.
|
||||||
if rest.startswith(os.path.sep) or rest.startswith('/'):
|
if rest.startswith(os.path.sep) or rest.startswith('/'):
|
||||||
# Remove a slash in cases it's still there.
|
# Remove a slash in cases it's still there.
|
||||||
@@ -254,7 +257,9 @@ def transform_path_to_dotted(sys_path, module_path):
|
|||||||
# is very strange and is probably a file that is called
|
# is very strange and is probably a file that is called
|
||||||
# `.py`.
|
# `.py`.
|
||||||
return
|
return
|
||||||
yield tuple(split)
|
# Stub folders for foo can end with foo-stubs. Just remove
|
||||||
|
# it.
|
||||||
|
yield tuple(re.sub(r'-stubs$', '', s) for s in split)
|
||||||
|
|
||||||
potential_solutions = tuple(iter_potential_solutions())
|
potential_solutions = tuple(iter_potential_solutions())
|
||||||
if not potential_solutions:
|
if not potential_solutions:
|
||||||
|
|||||||
+3
-38
@@ -1,12 +1,8 @@
|
|||||||
""" A universal module with functions / classes without dependencies. """
|
""" A universal module with functions / classes without dependencies. """
|
||||||
import sys
|
|
||||||
import contextlib
|
|
||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from jedi._compatibility import reraise
|
|
||||||
|
|
||||||
|
|
||||||
_sep = os.path.sep
|
_sep = os.path.sep
|
||||||
if os.path.altsep is not None:
|
if os.path.altsep is not None:
|
||||||
@@ -36,7 +32,6 @@ class UncaughtAttributeError(Exception):
|
|||||||
"""
|
"""
|
||||||
Important, because `__getattr__` and `hasattr` catch AttributeErrors
|
Important, because `__getattr__` and `hasattr` catch AttributeErrors
|
||||||
implicitly. This is really evil (mainly because of `__getattr__`).
|
implicitly. This is really evil (mainly because of `__getattr__`).
|
||||||
`hasattr` in Python 2 is even more evil, because it catches ALL exceptions.
|
|
||||||
Therefore this class originally had to be derived from `BaseException`
|
Therefore this class originally had to be derived from `BaseException`
|
||||||
instead of `Exception`. But because I removed relevant `hasattr` from
|
instead of `Exception`. But because I removed relevant `hasattr` from
|
||||||
the code base, we can now switch back to `Exception`.
|
the code base, we can now switch back to `Exception`.
|
||||||
@@ -65,21 +60,17 @@ def reraise_uncaught(func):
|
|||||||
difficult. This decorator is to help us getting there by changing
|
difficult. This decorator is to help us getting there by changing
|
||||||
`AttributeError` to `UncaughtAttributeError` to avoid unexpected catch.
|
`AttributeError` to `UncaughtAttributeError` to avoid unexpected catch.
|
||||||
This helps us noticing bugs earlier and facilitates debugging.
|
This helps us noticing bugs earlier and facilitates debugging.
|
||||||
|
|
||||||
.. note:: Treating StopIteration here is easy.
|
|
||||||
Add that feature when needed.
|
|
||||||
"""
|
"""
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwds):
|
def wrapper(*args, **kwds):
|
||||||
try:
|
try:
|
||||||
return func(*args, **kwds)
|
return func(*args, **kwds)
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
exc_info = sys.exc_info()
|
raise UncaughtAttributeError(e) from e
|
||||||
reraise(UncaughtAttributeError(exc_info[1]), exc_info[2])
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class PushBackIterator(object):
|
class PushBackIterator:
|
||||||
def __init__(self, iterator):
|
def __init__(self, iterator):
|
||||||
self.pushes = []
|
self.pushes = []
|
||||||
self.iterator = iterator
|
self.iterator = iterator
|
||||||
@@ -91,35 +82,9 @@ class PushBackIterator(object):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def next(self):
|
|
||||||
""" Python 2 Compatibility """
|
|
||||||
return self.__next__()
|
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
if self.pushes:
|
if self.pushes:
|
||||||
self.current = self.pushes.pop()
|
self.current = self.pushes.pop()
|
||||||
else:
|
else:
|
||||||
self.current = next(self.iterator)
|
self.current = next(self.iterator)
|
||||||
return self.current
|
return self.current
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def ignored(*exceptions):
|
|
||||||
"""
|
|
||||||
Value manager that ignores all of the specified exceptions. This will
|
|
||||||
be in the standard library starting with Python 3.4.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except exceptions:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def indent_block(text, indention=' '):
|
|
||||||
"""This function indents a text block with a default of four spaces."""
|
|
||||||
temp = ''
|
|
||||||
while text and text[-1] == '\n':
|
|
||||||
temp += text[-1]
|
|
||||||
text = text[:-1]
|
|
||||||
lines = text.split('\n')
|
|
||||||
return '\n'.join(map(lambda s: indention + s, lines)) + temp
|
|
||||||
Loaded 100 of 234 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user