mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 06:46:15 +00:00
Update all vendored and patched libraries
- Update pipfile and lockfile - Update licenses - Bump pathlib2 to latest version - Include scandir without compiled library dependency - Update vendoring script to remote scandir binaries going forward - Update pinned dependencies in vendor.txt Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
@@ -12,8 +12,9 @@ pytest-pypy = {path = "./tests/pytest-pypi", editable = true}
|
||||
pytest-tap = "*"
|
||||
flaky = "*"
|
||||
stdeb = {version="*", markers="sys_platform == 'linux'"}
|
||||
white = {version="*", markers="python_version >= '3.6'"}
|
||||
black = {version="*", markers="python_version >= '3.6'"}
|
||||
pytz = "*"
|
||||
towncrier = "*"
|
||||
|
||||
[packages]
|
||||
|
||||
|
||||
Generated
+103
-104
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "65984957a775e890014e30415431347075837fb78fc5940451d3fb4f4eae475c"
|
||||
"sha256": "c62a2508eed06b8d1e3d8328bcf6f985a0436f1f5026b48121fe31f01de357b0"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -29,27 +29,42 @@
|
||||
],
|
||||
"version": "==1.4"
|
||||
},
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
|
||||
"sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
|
||||
],
|
||||
"version": "==1.1.5"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
|
||||
"sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450"
|
||||
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
|
||||
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
|
||||
],
|
||||
"version": "==17.4.0"
|
||||
"version": "==18.1.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
|
||||
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
],
|
||||
"version": "==2.5.3"
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:0461c7a52b5beb378936bf642753dec7a45305c96c6129d540b9c53227121a5a",
|
||||
"sha256:7183263650ba3071034e90b40a1ea74abccbd32cf525cef6d7914479dbe7f2fb"
|
||||
"sha256:3efe92eafbde15f8ac06478de11cfb84e47504896ccdde64507e751d2f91ec3a",
|
||||
"sha256:fc26c4ab28c541fb824f59fa83d5702f75829495d5a1dee603b29bc4fbe79095"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==18.3a0"
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==18.6b2"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
@@ -81,13 +96,6 @@
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.3.9"
|
||||
},
|
||||
"configparser": {
|
||||
"hashes": [
|
||||
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
|
||||
],
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
@@ -96,16 +104,6 @@
|
||||
],
|
||||
"version": "==0.14"
|
||||
},
|
||||
"enum34": {
|
||||
"hashes": [
|
||||
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
|
||||
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
|
||||
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
|
||||
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
|
||||
],
|
||||
"markers": "python_version < '3.4'",
|
||||
"version": "==1.1.6"
|
||||
},
|
||||
"execnet": {
|
||||
"hashes": [
|
||||
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
|
||||
@@ -131,25 +129,17 @@
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:7fab1062d11dd0038434e790d18c5b9133fd9e6b7257d707c4578ccc1e38b67c",
|
||||
"sha256:b1883637bbee4dc7bc98d900792d0a304d609fce0f5bd9ca91d1b6457e5918dd"
|
||||
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
||||
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
|
||||
],
|
||||
"version": "==1.0"
|
||||
},
|
||||
"funcsigs": {
|
||||
"hashes": [
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.6"
|
||||
"version": "==2.7"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
@@ -158,6 +148,13 @@
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"incremental": {
|
||||
"hashes": [
|
||||
"sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f",
|
||||
"sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3"
|
||||
],
|
||||
"version": "==17.5.0"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
|
||||
@@ -194,25 +191,25 @@
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
|
||||
"sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
|
||||
"sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
|
||||
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
|
||||
"sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
|
||||
"sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
|
||||
],
|
||||
"version": "==4.1.0"
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"pathlib2": {
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e",
|
||||
"sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c"
|
||||
"sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
|
||||
"sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
|
||||
],
|
||||
"version": "==2.1.0"
|
||||
"version": "==17.1"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:4e8a0ed6a8705a26768f4c3da26026013b157821fe5f95881599556ea9d91c19",
|
||||
"sha256:dae4aaa78eafcad10ce2581fc34d694faa616727837fd8e55c1a00951ad6744f"
|
||||
"sha256:3747c6f017f2dc099986c325239661948f9f5176f6880d9fdef164cb664cd665",
|
||||
"sha256:a9c27eb8f0e24e786e544b2dbaedb729c9d8546342b5a6818d8eda098ad4340d"
|
||||
],
|
||||
"version": "==4.0.2"
|
||||
"version": "==4.0.4"
|
||||
},
|
||||
"pipenv": {
|
||||
"editable": true,
|
||||
@@ -242,8 +239,6 @@
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14",
|
||||
"sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e",
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
@@ -263,13 +258,25 @@
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
|
||||
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
|
||||
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
|
||||
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
|
||||
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
|
||||
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
|
||||
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:54713b26c97538db6ff0703a12b19aeaeb60b5e599de542e7fca0ec83b9038e8",
|
||||
"sha256:829230122facf05a5f81a6d4dfe6454a04978ea3746853b2b84567ecf8e5c526"
|
||||
"sha256:26838b2bc58620e01675485491504c3aa7ee0faf335c37fcd5f8731ca4319591",
|
||||
"sha256:32c49a69566aa7c333188149ad48b58ac11a426d5352ea3d8f6ce843f88199cb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.1"
|
||||
"version": "==3.6.1"
|
||||
},
|
||||
"pytest-forked": {
|
||||
"hashes": [
|
||||
@@ -300,24 +307,18 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
|
||||
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
|
||||
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0",
|
||||
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
|
||||
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
|
||||
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
|
||||
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
|
||||
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
|
||||
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda"
|
||||
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
|
||||
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
|
||||
],
|
||||
"version": "==2018.3"
|
||||
"index": "pypi",
|
||||
"version": "==2018.4"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"version": "==2.18.4"
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
"hashes": [
|
||||
@@ -350,10 +351,17 @@
|
||||
},
|
||||
"sphinx-click": {
|
||||
"hashes": [
|
||||
"sha256:0eef2d55ee4b5ebc448d8aa52e6084b5085fd2e7852d7571abb090411217c4df",
|
||||
"sha256:c2680d84e8608bf47141e16924f1a981c653caff0faefe47a1d41c1438fbd5b4"
|
||||
"sha256:2d1ead3335ee43c4959ed1dd2b29156de1e2709b6d45ba42497fcd8d334c9c2c",
|
||||
"sha256:b3973152d29fb86f0196e47878c96e2b8b1edbcc854268f3f82847277e5182cf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"sphinxcontrib-websupport": {
|
||||
"hashes": [
|
||||
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
|
||||
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"stdeb": {
|
||||
@@ -366,17 +374,31 @@
|
||||
},
|
||||
"tap.py": {
|
||||
"hashes": [
|
||||
"sha256:34e067d41988ce6d015c71d67f0b3025917f9a37dbc9b47aba5717a64a72b0f2",
|
||||
"sha256:af320cef616d27922e0d3db3d729edecc93fdcb17c2fdc26b3e3d3dafe4d72c7"
|
||||
"sha256:03accd27118473475b33b44703b223df2f148679b9b01b6ac59866df0b580073",
|
||||
"sha256:06416d376f0d398ab163674f30ea3b4a320957e4baa51793b8e86bdfdfeb857d"
|
||||
],
|
||||
"version": "==2.2"
|
||||
"version": "==2.4"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:8e86bd6ce8cc11b9620cb637466453d94f5d57ad86f17e98a98d1f73e3baab2d"
|
||||
],
|
||||
"version": "==0.9.4"
|
||||
},
|
||||
"towncrier": {
|
||||
"hashes": [
|
||||
"sha256:2b6da2c90c0f053232775d96e816687bf607062b9b2947f625f5586c357cbd59",
|
||||
"sha256:4643dafdde7d503cd6990dfba8043faa9f920fc4af0c2e69eb25488760534ac0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==18.5.0"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:22c760d05b2eb6e96a91ad7ed1b03c9c97e6256fb7716410c27c2cb3265a5913",
|
||||
"sha256:d7bfa112a55290a5e2d0c3263a09b17b24240686fbf20d1ef7c5e8edfbb71ad0"
|
||||
"sha256:224291ee0d8c52d91b037fd90806f48c79bcd9994d3b0abc9e44b946a908fccd",
|
||||
"sha256:77b8424d41b31e68f437c6dd9cd567aebc9a860507cb42fbd880a5f822d966fe"
|
||||
],
|
||||
"version": "==4.23.1"
|
||||
"version": "==4.23.4"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
@@ -388,24 +410,10 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"version": "==1.22"
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:1d7e241b431e7afce47e77f8843a276f652699d1fa4f93b9d8ce0076fd7b0b54",
|
||||
"sha256:e8e05d4714a1c51a2f5921e62f547fcb0f713ebbe959e0a7f585cc8bef71d11f"
|
||||
],
|
||||
"version": "==15.2.0"
|
||||
},
|
||||
"virtualenv-clone": {
|
||||
"hashes": [
|
||||
"sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7",
|
||||
"sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8"
|
||||
],
|
||||
"version": "==0.3.0"
|
||||
"version": "==1.23"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
@@ -413,15 +421,6 @@
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
},
|
||||
"white": {
|
||||
"hashes": [
|
||||
"sha256:45e2c7f54de1facc60bf0a726b480cdc43422aad57c3a0bc5ba54cb536696683",
|
||||
"sha256:bca98066256cfff6fb85ec36b95cc5913c888c170a8407c340786972b06c6f8f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ def is_manylinux1_compatible():
|
||||
pass
|
||||
|
||||
# Check glibc version. CentOS 5 uses glibc 2.5.
|
||||
return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 5)
|
||||
return pip._internal.utils.glibc.have_compatible_glibc(2, 5)
|
||||
|
||||
|
||||
def get_darwin_arches(major, minor, machine):
|
||||
|
||||
@@ -22,7 +22,7 @@ def do_import(module_path, subimport=None, old_path=None, vendored_name=None):
|
||||
if subimport:
|
||||
return getattr(_tmp, subimport, _tmp)
|
||||
return _tmp
|
||||
|
||||
|
||||
|
||||
InstallRequirement = do_import('req.req_install', 'InstallRequirement', vendored_name='notpip')
|
||||
parse_requirements = do_import('req.req_file', 'parse_requirements', vendored_name='notpip')
|
||||
|
||||
@@ -22,10 +22,10 @@ from .._compat import (
|
||||
InstallationError,
|
||||
)
|
||||
|
||||
from notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
from notpip._vendor.packaging.version import Version, InvalidVersion, parse as parse_version
|
||||
from notpip._vendor.packaging.specifiers import SpecifierSet
|
||||
from notpip._vendor.pyparsing import ParseException
|
||||
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
from pipenv.patched.notpip._vendor.packaging.version import Version, InvalidVersion, parse as parse_version
|
||||
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pipenv.patched.notpip._vendor.pyparsing import ParseException
|
||||
|
||||
from ..cache import CACHE_DIR
|
||||
from pipenv.environments import PIPENV_CACHE_DIR
|
||||
@@ -37,15 +37,15 @@ from .base import BaseRepository
|
||||
|
||||
|
||||
try:
|
||||
from notpip._internal.operations.prepare import RequirementPreparer
|
||||
from notpip._internal.resolve import Resolver as PipResolver
|
||||
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
|
||||
from pipenv.patched.notpip._internal.resolve import Resolver as PipResolver
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from notpip._internal.cache import WheelCache
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache
|
||||
except ImportError:
|
||||
from notpip.wheel import WheelCache
|
||||
from pipenv.patched.notpip.wheel import WheelCache
|
||||
|
||||
|
||||
class HashCache(SafeFileCache):
|
||||
|
||||
@@ -8,7 +8,7 @@ from itertools import chain, count
|
||||
import os
|
||||
|
||||
from first import first
|
||||
from notpip._vendor.packaging.markers import default_environment
|
||||
from pipenv.patched.notpip._vendor.packaging.markers import default_environment
|
||||
from ._compat import InstallRequirement
|
||||
|
||||
from . import click
|
||||
@@ -314,7 +314,7 @@ class Resolver(object):
|
||||
dependency_strings = self.dependency_cache[ireq]
|
||||
log.debug(' {:25} requires {}'.format(format_requirement(ireq),
|
||||
', '.join(sorted(dependency_strings, key=lambda s: s.lower())) or '-'))
|
||||
from notpip._vendor.packaging.markers import InvalidMarker
|
||||
from pipenv.patched.notpip._vendor.packaging.markers import InvalidMarker
|
||||
for dependency_string in dependency_strings:
|
||||
try:
|
||||
_dependency_string = dependency_string
|
||||
|
||||
Vendored
+4
-2
@@ -6,11 +6,12 @@ from . import converters, exceptions, filters, validators
|
||||
from ._config import get_run_validators, set_run_validators
|
||||
from ._funcs import asdict, assoc, astuple, evolve, has
|
||||
from ._make import (
|
||||
NOTHING, Attribute, Factory, attrib, attrs, fields, make_class, validate
|
||||
NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict,
|
||||
make_class, validate
|
||||
)
|
||||
|
||||
|
||||
__version__ = "17.4.0"
|
||||
__version__ = "18.1.0"
|
||||
|
||||
__title__ = "attrs"
|
||||
__description__ = "Classes Without Boilerplate"
|
||||
@@ -43,6 +44,7 @@ __all__ = [
|
||||
"evolve",
|
||||
"exceptions",
|
||||
"fields",
|
||||
"fields_dict",
|
||||
"filters",
|
||||
"get_run_validators",
|
||||
"has",
|
||||
|
||||
Vendored
+16
-9
@@ -10,6 +10,13 @@ PY2 = sys.version_info[0] == 2
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
if PYPY or sys.version_info[:2] >= (3, 6):
|
||||
ordered_dict = dict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
ordered_dict = OrderedDict
|
||||
|
||||
|
||||
if PY2:
|
||||
from UserDict import IterableUserDict
|
||||
|
||||
@@ -87,15 +94,12 @@ else:
|
||||
return types.MappingProxyType(dict(d))
|
||||
|
||||
|
||||
def import_ctypes(): # pragma: nocover
|
||||
def import_ctypes():
|
||||
"""
|
||||
Moved into a function for testability.
|
||||
"""
|
||||
try:
|
||||
import ctypes
|
||||
return ctypes
|
||||
except ImportError:
|
||||
return None
|
||||
import ctypes
|
||||
return ctypes
|
||||
|
||||
|
||||
if not PY2:
|
||||
@@ -126,12 +130,15 @@ def make_set_closure_cell():
|
||||
def set_closure_cell(cell, value):
|
||||
cell.__setstate__((value,))
|
||||
else:
|
||||
ctypes = import_ctypes()
|
||||
if ctypes is not None:
|
||||
try:
|
||||
ctypes = import_ctypes()
|
||||
|
||||
set_closure_cell = ctypes.pythonapi.PyCell_Set
|
||||
set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
|
||||
set_closure_cell.restype = ctypes.c_int
|
||||
else:
|
||||
except Exception:
|
||||
# We try best effort to set the cell, but sometimes it's not
|
||||
# possible. For example on Jython or on GAE.
|
||||
set_closure_cell = just_warn
|
||||
return set_closure_cell
|
||||
|
||||
|
||||
Vendored
+245
-67
@@ -3,12 +3,15 @@ from __future__ import absolute_import, division, print_function
|
||||
import hashlib
|
||||
import linecache
|
||||
import sys
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
from . import _config
|
||||
from ._compat import PY2, isclass, iteritems, metadata_proxy, set_closure_cell
|
||||
from ._compat import (
|
||||
PY2, isclass, iteritems, metadata_proxy, ordered_dict, set_closure_cell
|
||||
)
|
||||
from .exceptions import (
|
||||
DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError,
|
||||
UnannotatedAttributeError
|
||||
@@ -20,6 +23,8 @@ _obj_setattr = object.__setattr__
|
||||
_init_converter_pat = "__attr_converter_{}"
|
||||
_init_factory_pat = "__attr_factory_{}"
|
||||
_tuple_property_pat = " {attr_name} = property(itemgetter({index}))"
|
||||
_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar")
|
||||
|
||||
_empty_metadata_singleton = metadata_proxy({})
|
||||
|
||||
|
||||
@@ -45,7 +50,7 @@ class _Nothing(object):
|
||||
return "NOTHING"
|
||||
|
||||
def __hash__(self):
|
||||
return 0xdeadbeef
|
||||
return 0xc0ffee
|
||||
|
||||
|
||||
NOTHING = _Nothing()
|
||||
@@ -56,7 +61,8 @@ Sentinel to indicate the lack of a value when ``None`` is ambiguous.
|
||||
|
||||
def attrib(default=NOTHING, validator=None,
|
||||
repr=True, cmp=True, hash=None, init=True,
|
||||
convert=None, metadata=None, type=None, converter=None):
|
||||
convert=None, metadata=None, type=None, converter=None,
|
||||
factory=None):
|
||||
"""
|
||||
Create a new attribute on a class.
|
||||
|
||||
@@ -81,6 +87,9 @@ def attrib(default=NOTHING, validator=None,
|
||||
|
||||
:type default: Any value.
|
||||
|
||||
:param callable factory: Syntactic sugar for
|
||||
``default=attr.Factory(callable)``.
|
||||
|
||||
:param validator: :func:`callable` that is called by ``attrs``-generated
|
||||
``__init__`` methods after the instance has been initialized. They
|
||||
receive the initialized instance, the :class:`Attribute`, and the
|
||||
@@ -97,7 +106,7 @@ def attrib(default=NOTHING, validator=None,
|
||||
|
||||
The validator can also be set using decorator notation as shown below.
|
||||
|
||||
:type validator: ``callable`` or a ``list`` of ``callable``\ s.
|
||||
:type validator: ``callable`` or a ``list`` of ``callable``\\ s.
|
||||
|
||||
:param bool repr: Include this attribute in the generated ``__repr__``
|
||||
method.
|
||||
@@ -128,13 +137,15 @@ def attrib(default=NOTHING, validator=None,
|
||||
|
||||
.. versionadded:: 15.2.0 *convert*
|
||||
.. versionadded:: 16.3.0 *metadata*
|
||||
.. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
|
||||
.. versionchanged:: 17.1.0
|
||||
*hash* is ``None`` and therefore mirrors *cmp* by default.
|
||||
.. versionadded:: 17.3.0 *type*
|
||||
.. deprecated:: 17.4.0 *convert*
|
||||
.. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
|
||||
*convert* to achieve consistency with other noun-based arguments.
|
||||
.. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
|
||||
.. versionchanged:: 17.1.0
|
||||
*hash* is ``None`` and therefore mirrors *cmp* by default.
|
||||
.. versionadded:: 17.3.0 *type*
|
||||
.. deprecated:: 17.4.0 *convert*
|
||||
.. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
|
||||
*convert* to achieve consistency with other noun-based arguments.
|
||||
.. versionadded:: 18.1.0
|
||||
``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
|
||||
"""
|
||||
if hash is not None and hash is not True and hash is not False:
|
||||
raise TypeError(
|
||||
@@ -154,6 +165,18 @@ def attrib(default=NOTHING, validator=None,
|
||||
)
|
||||
converter = convert
|
||||
|
||||
if factory is not None:
|
||||
if default is not NOTHING:
|
||||
raise ValueError(
|
||||
"The `default` and `factory` arguments are mutually "
|
||||
"exclusive."
|
||||
)
|
||||
if not callable(factory):
|
||||
raise ValueError(
|
||||
"The `factory` argument must be a callable."
|
||||
)
|
||||
default = Factory(factory)
|
||||
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
|
||||
@@ -201,8 +224,9 @@ def _make_attr_tuple_class(cls_name, attr_names):
|
||||
# Tuple class for extracted attributes from a class definition.
|
||||
# `super_attrs` is a subset of `attrs`.
|
||||
_Attributes = _make_attr_tuple_class("_Attributes", [
|
||||
"attrs", # all attributes to build dunder methods for
|
||||
"super_attrs", # attributes that have been inherited from super classes
|
||||
"attrs", # all attributes to build dunder methods for
|
||||
"super_attrs", # attributes that have been inherited
|
||||
"super_attrs_map", # map inherited attributes to their originating classes
|
||||
])
|
||||
|
||||
|
||||
@@ -210,10 +234,11 @@ def _is_class_var(annot):
|
||||
"""
|
||||
Check whether *annot* is a typing.ClassVar.
|
||||
|
||||
The implementation is gross but importing `typing` is slow and there are
|
||||
discussions to remove it from the stdlib alltogether.
|
||||
The string comparison hack is used to avoid evaluating all string
|
||||
annotations which would put attrs-based classes at a performance
|
||||
disadvantage compared to plain old classes.
|
||||
"""
|
||||
return str(annot).startswith("typing.ClassVar")
|
||||
return str(annot).startswith(_classvar_prefixes)
|
||||
|
||||
|
||||
def _get_annotations(cls):
|
||||
@@ -232,6 +257,13 @@ def _get_annotations(cls):
|
||||
return anns
|
||||
|
||||
|
||||
def _counter_getter(e):
|
||||
"""
|
||||
Key function for sorting to avoid re-creating a lambda for every class.
|
||||
"""
|
||||
return e[1].counter
|
||||
|
||||
|
||||
def _transform_attrs(cls, these, auto_attribs):
|
||||
"""
|
||||
Transform all `_CountingAttr`s on a class into `Attribute`s.
|
||||
@@ -244,11 +276,14 @@ def _transform_attrs(cls, these, auto_attribs):
|
||||
anns = _get_annotations(cls)
|
||||
|
||||
if these is not None:
|
||||
ca_list = sorted((
|
||||
ca_list = [
|
||||
(name, ca)
|
||||
for name, ca
|
||||
in iteritems(these)
|
||||
), key=lambda e: e[1].counter)
|
||||
]
|
||||
|
||||
if not isinstance(these, ordered_dict):
|
||||
ca_list.sort(key=_counter_getter)
|
||||
elif auto_attribs is True:
|
||||
ca_names = {
|
||||
name
|
||||
@@ -298,6 +333,7 @@ def _transform_attrs(cls, these, auto_attribs):
|
||||
]
|
||||
|
||||
super_attrs = []
|
||||
super_attr_map = {} # A dictionary of superattrs to their classes.
|
||||
taken_attr_names = {a.name: a for a in own_attrs}
|
||||
|
||||
# Traverse the MRO and collect attributes.
|
||||
@@ -311,6 +347,7 @@ def _transform_attrs(cls, these, auto_attribs):
|
||||
if prev_a is None:
|
||||
super_attrs.append(a)
|
||||
taken_attr_names[a.name] = a
|
||||
super_attr_map[a.name] = super_cls
|
||||
|
||||
attr_names = [a.name for a in super_attrs + own_attrs]
|
||||
|
||||
@@ -341,7 +378,7 @@ def _transform_attrs(cls, these, auto_attribs):
|
||||
a.init is not False:
|
||||
had_default = True
|
||||
|
||||
return _Attributes((attrs, super_attrs))
|
||||
return _Attributes((attrs, super_attrs, super_attr_map))
|
||||
|
||||
|
||||
def _frozen_setattrs(self, name, value):
|
||||
@@ -364,20 +401,24 @@ class _ClassBuilder(object):
|
||||
"""
|
||||
__slots__ = (
|
||||
"_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots",
|
||||
"_frozen", "_has_post_init",
|
||||
"_frozen", "_has_post_init", "_delete_attribs", "_super_attr_map",
|
||||
)
|
||||
|
||||
def __init__(self, cls, these, slots, frozen, auto_attribs):
|
||||
attrs, super_attrs = _transform_attrs(cls, these, auto_attribs)
|
||||
attrs, super_attrs, super_map = _transform_attrs(
|
||||
cls, these, auto_attribs
|
||||
)
|
||||
|
||||
self._cls = cls
|
||||
self._cls_dict = dict(cls.__dict__) if slots else {}
|
||||
self._attrs = attrs
|
||||
self._super_names = set(a.name for a in super_attrs)
|
||||
self._super_attr_map = super_map
|
||||
self._attr_names = tuple(a.name for a in attrs)
|
||||
self._slots = slots
|
||||
self._frozen = frozen or _has_frozen_superclass(cls)
|
||||
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
|
||||
self._delete_attribs = not bool(these)
|
||||
|
||||
self._cls_dict["__attrs_attrs__"] = self._attrs
|
||||
|
||||
@@ -407,10 +448,11 @@ class _ClassBuilder(object):
|
||||
super_names = self._super_names
|
||||
|
||||
# Clean class of attribute definitions (`attr.ib()`s).
|
||||
for name in self._attr_names:
|
||||
if name not in super_names and \
|
||||
getattr(cls, name, None) is not None:
|
||||
delattr(cls, name)
|
||||
if self._delete_attribs:
|
||||
for name in self._attr_names:
|
||||
if name not in super_names and \
|
||||
getattr(cls, name, None) is not None:
|
||||
delattr(cls, name)
|
||||
|
||||
# Attach our dunder methods.
|
||||
for name, value in self._cls_dict.items():
|
||||
@@ -441,20 +483,26 @@ class _ClassBuilder(object):
|
||||
if qualname is not None:
|
||||
cd["__qualname__"] = qualname
|
||||
|
||||
attr_names = tuple(self._attr_names)
|
||||
# __weakref__ is not writable.
|
||||
state_attr_names = tuple(
|
||||
an for an in self._attr_names if an != "__weakref__"
|
||||
)
|
||||
|
||||
def slots_getstate(self):
|
||||
"""
|
||||
Automatically created by attrs.
|
||||
"""
|
||||
return tuple(getattr(self, name) for name in attr_names)
|
||||
return tuple(
|
||||
getattr(self, name)
|
||||
for name in state_attr_names
|
||||
)
|
||||
|
||||
def slots_setstate(self, state):
|
||||
"""
|
||||
Automatically created by attrs.
|
||||
"""
|
||||
__bound_setattr = _obj_setattr.__get__(self, Attribute)
|
||||
for name, value in zip(attr_names, state):
|
||||
for name, value in zip(state_attr_names, state):
|
||||
__bound_setattr(name, value)
|
||||
|
||||
# slots and frozen require __getstate__/__setstate__ to work
|
||||
@@ -526,6 +574,8 @@ class _ClassBuilder(object):
|
||||
self._attrs,
|
||||
self._has_post_init,
|
||||
self._frozen,
|
||||
self._slots,
|
||||
self._super_attr_map,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -575,7 +625,12 @@ def attrs(maybe_cls=None, these=None, repr_ns=None,
|
||||
Django models) or don't want to.
|
||||
|
||||
If *these* is not ``None``, ``attrs`` will *not* search the class body
|
||||
for attributes.
|
||||
for attributes and will *not* remove any attributes from it.
|
||||
|
||||
If *these* is an ordered dict (:class:`dict` on Python 3.6+,
|
||||
:class:`collections.OrderedDict` otherwise), the order is deduced from
|
||||
the order of the attributes inside *these*. Otherwise the order
|
||||
of the definition of the attributes is used.
|
||||
|
||||
:type these: :class:`dict` of :class:`str` to :func:`attr.ib`
|
||||
|
||||
@@ -656,13 +711,16 @@ def attrs(maybe_cls=None, these=None, repr_ns=None,
|
||||
|
||||
.. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/
|
||||
|
||||
.. versionadded:: 16.0.0 *slots*
|
||||
.. versionadded:: 16.1.0 *frozen*
|
||||
.. versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``.
|
||||
.. versionchanged::
|
||||
17.1.0 *hash* supports ``None`` as value which is also the default
|
||||
now.
|
||||
.. versionadded:: 16.0.0 *slots*
|
||||
.. versionadded:: 16.1.0 *frozen*
|
||||
.. versionadded:: 16.3.0 *str*
|
||||
.. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
|
||||
.. versionchanged:: 17.1.0
|
||||
*hash* supports ``None`` as value which is also the default now.
|
||||
.. versionadded:: 17.3.0 *auto_attribs*
|
||||
.. versionchanged:: 18.1.0
|
||||
If *these* is passed, no attributes are deleted from the class body.
|
||||
.. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
|
||||
"""
|
||||
def wrap(cls):
|
||||
if getattr(cls, "__class__", None) is None:
|
||||
@@ -899,6 +957,9 @@ def _add_cmp(cls, attrs=None):
|
||||
return cls
|
||||
|
||||
|
||||
_already_repring = threading.local()
|
||||
|
||||
|
||||
def _make_repr(attrs, ns):
|
||||
"""
|
||||
Make a repr method for *attr_names* adding *ns* to the full name.
|
||||
@@ -913,6 +974,14 @@ def _make_repr(attrs, ns):
|
||||
"""
|
||||
Automatically created by attrs.
|
||||
"""
|
||||
try:
|
||||
working_set = _already_repring.working_set
|
||||
except AttributeError:
|
||||
working_set = set()
|
||||
_already_repring.working_set = working_set
|
||||
|
||||
if id(self) in working_set:
|
||||
return "..."
|
||||
real_cls = self.__class__
|
||||
if ns is None:
|
||||
qualname = getattr(real_cls, "__qualname__", None)
|
||||
@@ -923,13 +992,23 @@ def _make_repr(attrs, ns):
|
||||
else:
|
||||
class_name = ns + "." + real_cls.__name__
|
||||
|
||||
return "{0}({1})".format(
|
||||
class_name,
|
||||
", ".join(
|
||||
name + "=" + repr(getattr(self, name, NOTHING))
|
||||
for name in attr_names
|
||||
)
|
||||
)
|
||||
# Since 'self' remains on the stack (i.e.: strongly referenced) for the
|
||||
# duration of this call, it's safe to depend on id(...) stability, and
|
||||
# not need to track the instance and therefore worry about properties
|
||||
# like weakref- or hash-ability.
|
||||
working_set.add(id(self))
|
||||
try:
|
||||
result = [class_name, "("]
|
||||
first = True
|
||||
for name in attr_names:
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
result.append(", ")
|
||||
result.extend((name, "=", repr(getattr(self, name, NOTHING))))
|
||||
return "".join(result) + ")"
|
||||
finally:
|
||||
working_set.remove(id(self))
|
||||
return __repr__
|
||||
|
||||
|
||||
@@ -944,7 +1023,7 @@ def _add_repr(cls, ns=None, attrs=None):
|
||||
return cls
|
||||
|
||||
|
||||
def _make_init(attrs, post_init, frozen):
|
||||
def _make_init(attrs, post_init, frozen, slots, super_attr_map):
|
||||
attrs = [
|
||||
a
|
||||
for a in attrs
|
||||
@@ -958,10 +1037,12 @@ def _make_init(attrs, post_init, frozen):
|
||||
sha1.hexdigest()
|
||||
)
|
||||
|
||||
script, globs = _attrs_to_init_script(
|
||||
script, globs, annotations = _attrs_to_init_script(
|
||||
attrs,
|
||||
frozen,
|
||||
slots,
|
||||
post_init,
|
||||
super_attr_map,
|
||||
)
|
||||
locs = {}
|
||||
bytecode = compile(script, unique_filename, "exec")
|
||||
@@ -985,7 +1066,9 @@ def _make_init(attrs, post_init, frozen):
|
||||
unique_filename,
|
||||
)
|
||||
|
||||
return locs["__init__"]
|
||||
__init__ = locs["__init__"]
|
||||
__init__.__annotations__ = annotations
|
||||
return __init__
|
||||
|
||||
|
||||
def _add_init(cls, frozen):
|
||||
@@ -996,13 +1079,15 @@ def _add_init(cls, frozen):
|
||||
cls.__attrs_attrs__,
|
||||
getattr(cls, "__attrs_post_init__", False),
|
||||
frozen,
|
||||
_is_slot_cls(cls),
|
||||
{},
|
||||
)
|
||||
return cls
|
||||
|
||||
|
||||
def fields(cls):
|
||||
"""
|
||||
Returns the tuple of ``attrs`` attributes for a class.
|
||||
Return the tuple of ``attrs`` attributes for a class.
|
||||
|
||||
The tuple also allows accessing the fields by their names (see below for
|
||||
examples).
|
||||
@@ -1028,6 +1113,34 @@ def fields(cls):
|
||||
return attrs
|
||||
|
||||
|
||||
def fields_dict(cls):
|
||||
"""
|
||||
Return an ordered dictionary of ``attrs`` attributes for a class, whose
|
||||
keys are the attribute names.
|
||||
|
||||
:param type cls: Class to introspect.
|
||||
|
||||
:raise TypeError: If *cls* is not a class.
|
||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
||||
class.
|
||||
|
||||
:rtype: an ordered dict where keys are attribute names and values are
|
||||
:class:`attr.Attribute`\\ s. This will be a :class:`dict` if it's
|
||||
naturally ordered like on Python 3.6+ or an
|
||||
:class:`~collections.OrderedDict` otherwise.
|
||||
|
||||
.. versionadded:: 18.1.0
|
||||
"""
|
||||
if not isclass(cls):
|
||||
raise TypeError("Passed object must be a class.")
|
||||
attrs = getattr(cls, "__attrs_attrs__", None)
|
||||
if attrs is None:
|
||||
raise NotAnAttrsClassError(
|
||||
"{cls!r} is not an attrs-decorated class.".format(cls=cls)
|
||||
)
|
||||
return ordered_dict(((a.name, a) for a in attrs))
|
||||
|
||||
|
||||
def validate(inst):
|
||||
"""
|
||||
Validate all attributes on *inst* that have a validator.
|
||||
@@ -1045,37 +1158,92 @@ def validate(inst):
|
||||
v(inst, a, getattr(inst, a.name))
|
||||
|
||||
|
||||
def _attrs_to_init_script(attrs, frozen, post_init):
|
||||
def _is_slot_cls(cls):
|
||||
return "__slots__" in cls.__dict__
|
||||
|
||||
|
||||
def _is_slot_attr(a_name, super_attr_map):
|
||||
"""
|
||||
Check if the attribute name comes from a slot class.
|
||||
"""
|
||||
return a_name in super_attr_map and _is_slot_cls(super_attr_map[a_name])
|
||||
|
||||
|
||||
def _attrs_to_init_script(attrs, frozen, slots, post_init, super_attr_map):
|
||||
"""
|
||||
Return a script of an initializer for *attrs* and a dict of globals.
|
||||
|
||||
The globals are expected by the generated script.
|
||||
|
||||
If *frozen* is True, we cannot set the attributes directly so we use
|
||||
If *frozen* is True, we cannot set the attributes directly so we use
|
||||
a cached ``object.__setattr__``.
|
||||
"""
|
||||
lines = []
|
||||
any_slot_ancestors = any(
|
||||
_is_slot_attr(a.name, super_attr_map)
|
||||
for a in attrs
|
||||
)
|
||||
if frozen is True:
|
||||
lines.append(
|
||||
# Circumvent the __setattr__ descriptor to save one lookup per
|
||||
# assignment.
|
||||
"_setattr = _cached_setattr.__get__(self, self.__class__)"
|
||||
)
|
||||
if slots is True:
|
||||
lines.append(
|
||||
# Circumvent the __setattr__ descriptor to save one lookup per
|
||||
# assignment.
|
||||
"_setattr = _cached_setattr.__get__(self, self.__class__)"
|
||||
)
|
||||
|
||||
def fmt_setter(attr_name, value_var):
|
||||
return "_setattr('%(attr_name)s', %(value_var)s)" % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
}
|
||||
def fmt_setter(attr_name, value_var):
|
||||
return "_setattr('%(attr_name)s', %(value_var)s)" % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
}
|
||||
|
||||
def fmt_setter_with_converter(attr_name, value_var):
|
||||
conv_name = _init_converter_pat.format(attr_name)
|
||||
return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
"conv": conv_name,
|
||||
}
|
||||
def fmt_setter_with_converter(attr_name, value_var):
|
||||
conv_name = _init_converter_pat.format(attr_name)
|
||||
return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
"conv": conv_name,
|
||||
}
|
||||
else:
|
||||
# Dict frozen classes assign directly to __dict__.
|
||||
# But only if the attribute doesn't come from an ancestor slot
|
||||
# class.
|
||||
lines.append(
|
||||
"_inst_dict = self.__dict__"
|
||||
)
|
||||
if any_slot_ancestors:
|
||||
lines.append(
|
||||
# Circumvent the __setattr__ descriptor to save one lookup
|
||||
# per assignment.
|
||||
"_setattr = _cached_setattr.__get__(self, self.__class__)"
|
||||
)
|
||||
|
||||
def fmt_setter(attr_name, value_var):
|
||||
if _is_slot_attr(attr_name, super_attr_map):
|
||||
res = "_setattr('%(attr_name)s', %(value_var)s)" % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
}
|
||||
else:
|
||||
res = "_inst_dict['%(attr_name)s'] = %(value_var)s" % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
}
|
||||
return res
|
||||
|
||||
def fmt_setter_with_converter(attr_name, value_var):
|
||||
conv_name = _init_converter_pat.format(attr_name)
|
||||
if _is_slot_attr(attr_name, super_attr_map):
|
||||
tmpl = "_setattr('%(attr_name)s', %(c)s(%(value_var)s))"
|
||||
else:
|
||||
tmpl = "_inst_dict['%(attr_name)s'] = %(c)s(%(value_var)s)"
|
||||
return tmpl % {
|
||||
"attr_name": attr_name,
|
||||
"value_var": value_var,
|
||||
"c": conv_name,
|
||||
}
|
||||
else:
|
||||
# Not frozen.
|
||||
def fmt_setter(attr_name, value):
|
||||
return "self.%(attr_name)s = %(value)s" % {
|
||||
"attr_name": attr_name,
|
||||
@@ -1096,6 +1264,7 @@ def _attrs_to_init_script(attrs, frozen, post_init):
|
||||
# This is a dictionary of names to validator and converter callables.
|
||||
# Injecting this into __init__ globals lets us avoid lookups.
|
||||
names_for_globals = {}
|
||||
annotations = {'return': None}
|
||||
|
||||
for a in attrs:
|
||||
if a.validator:
|
||||
@@ -1186,6 +1355,9 @@ def _attrs_to_init_script(attrs, frozen, post_init):
|
||||
else:
|
||||
lines.append(fmt_setter(attr_name, arg_name))
|
||||
|
||||
if a.init is True and a.converter is None and a.type is not None:
|
||||
annotations[arg_name] = a.type
|
||||
|
||||
if attrs_to_validate: # we can skip this if there are no validators.
|
||||
names_for_globals["_config"] = _config
|
||||
lines.append("if _config._run_validators is True:")
|
||||
@@ -1205,7 +1377,7 @@ def __init__(self, {args}):
|
||||
""".format(
|
||||
args=", ".join(args),
|
||||
lines="\n ".join(lines) if lines else "pass",
|
||||
), names_for_globals
|
||||
), names_for_globals, annotations
|
||||
|
||||
|
||||
class Attribute(object):
|
||||
@@ -1436,6 +1608,11 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
|
||||
|
||||
:param attrs: A list of names or a dictionary of mappings of names to
|
||||
attributes.
|
||||
|
||||
If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+,
|
||||
:class:`collections.OrderedDict` otherwise), the order is deduced from
|
||||
the order of the names or attributes inside *attrs*. Otherwise the
|
||||
order of the definition of the attributes is used.
|
||||
:type attrs: :class:`list` or :class:`dict`
|
||||
|
||||
:param tuple bases: Classes that the new class will subclass.
|
||||
@@ -1445,7 +1622,8 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
|
||||
:return: A new class with *attrs*.
|
||||
:rtype: type
|
||||
|
||||
.. versionadded:: 17.1.0 *bases*
|
||||
.. versionadded:: 17.1.0 *bases*
|
||||
.. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
|
||||
"""
|
||||
if isinstance(attrs, dict):
|
||||
cls_dict = attrs
|
||||
|
||||
Vendored
+2
-2
@@ -23,7 +23,7 @@ def include(*what):
|
||||
Whitelist *what*.
|
||||
|
||||
:param what: What to whitelist.
|
||||
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
|
||||
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\\ s
|
||||
|
||||
:rtype: :class:`callable`
|
||||
"""
|
||||
@@ -40,7 +40,7 @@ def exclude(*what):
|
||||
Blacklist *what*.
|
||||
|
||||
:param what: What to blacklist.
|
||||
:type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
|
||||
:type what: :class:`list` of classes or :class:`attr.Attribute`\\ s.
|
||||
|
||||
:rtype: :class:`callable`
|
||||
"""
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
from .core import where, old_where
|
||||
|
||||
__version__ = "2018.01.18"
|
||||
__version__ = "2018.04.16"
|
||||
|
||||
Vendored
-33
@@ -3483,39 +3483,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
|
||||
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5 O=T\xdcRKTRUST Bilgi \u0130leti\u015fim ve Bili\u015fim G\xfcvenli\u011fi Hizmetleri A.\u015e.
|
||||
# Subject: CN=T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5 O=T\xdcRKTRUST Bilgi \u0130leti\u015fim ve Bili\u015fim G\xfcvenli\u011fi Hizmetleri A.\u015e.
|
||||
# Label: "T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5"
|
||||
# Serial: 156233699172481
|
||||
# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e
|
||||
# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb
|
||||
# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE
|
||||
BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn
|
||||
aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg
|
||||
QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg
|
||||
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0
|
||||
MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD
|
||||
VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
|
||||
dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF
|
||||
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom
|
||||
/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR
|
||||
Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3
|
||||
4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z
|
||||
5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0
|
||||
hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID
|
||||
AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/
|
||||
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX
|
||||
SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l
|
||||
VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq
|
||||
URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf
|
||||
peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF
|
||||
Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW
|
||||
+qtB4Uu2NQvAmxU=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
|
||||
# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
|
||||
# Label: "Certinomis - Root CA"
|
||||
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright (c) 2013-2017, Kim Davies. All rights reserved.
|
||||
Copyright (c) 2013-2018, Kim Davies. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
+26
-14
@@ -34,7 +34,11 @@ class InvalidCodepointContext(IDNAError):
|
||||
|
||||
|
||||
def _combining_class(cp):
|
||||
return unicodedata.combining(unichr(cp))
|
||||
v = unicodedata.combining(unichr(cp))
|
||||
if v == 0:
|
||||
if not unicodedata.name(unichr(cp)):
|
||||
raise ValueError("Unknown character in unicodedata")
|
||||
return v
|
||||
|
||||
def _is_script(cp, script):
|
||||
return intranges_contain(ord(cp), idnadata.scripts[script])
|
||||
@@ -71,7 +75,6 @@ def check_bidi(label, check_ltr=False):
|
||||
raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx))
|
||||
if direction in ['R', 'AL', 'AN']:
|
||||
bidi_label = True
|
||||
break
|
||||
if not bidi_label and not check_ltr:
|
||||
return True
|
||||
|
||||
@@ -244,8 +247,13 @@ def check_label(label):
|
||||
if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']):
|
||||
continue
|
||||
elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']):
|
||||
if not valid_contextj(label, pos):
|
||||
raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label)))
|
||||
try:
|
||||
if not valid_contextj(label, pos):
|
||||
raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format(
|
||||
_unot(cp_value), pos+1, repr(label)))
|
||||
except ValueError:
|
||||
raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format(
|
||||
_unot(cp_value), pos+1, repr(label)))
|
||||
elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']):
|
||||
if not valid_contexto(label, pos):
|
||||
raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label)))
|
||||
@@ -317,10 +325,10 @@ def uts46_remap(domain, std3_rules=True, transitional=False):
|
||||
replacement = uts46row[2] if len(uts46row) == 3 else None
|
||||
if (status == "V" or
|
||||
(status == "D" and not transitional) or
|
||||
(status == "3" and std3_rules and replacement is None)):
|
||||
(status == "3" and not std3_rules and replacement is None)):
|
||||
output += char
|
||||
elif replacement is not None and (status == "M" or
|
||||
(status == "3" and std3_rules) or
|
||||
(status == "3" and not std3_rules) or
|
||||
(status == "D" and transitional)):
|
||||
output += replacement
|
||||
elif status != "I":
|
||||
@@ -344,15 +352,17 @@ def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False):
|
||||
labels = s.split('.')
|
||||
else:
|
||||
labels = _unicode_dots_re.split(s)
|
||||
while labels and not labels[0]:
|
||||
del labels[0]
|
||||
if not labels:
|
||||
if not labels or labels == ['']:
|
||||
raise IDNAError('Empty domain')
|
||||
if labels[-1] == '':
|
||||
del labels[-1]
|
||||
trailing_dot = True
|
||||
for label in labels:
|
||||
result.append(alabel(label))
|
||||
s = alabel(label)
|
||||
if s:
|
||||
result.append(s)
|
||||
else:
|
||||
raise IDNAError('Empty label')
|
||||
if trailing_dot:
|
||||
result.append(b'')
|
||||
s = b'.'.join(result)
|
||||
@@ -373,15 +383,17 @@ def decode(s, strict=False, uts46=False, std3_rules=False):
|
||||
labels = _unicode_dots_re.split(s)
|
||||
else:
|
||||
labels = s.split(u'.')
|
||||
while labels and not labels[0]:
|
||||
del labels[0]
|
||||
if not labels:
|
||||
if not labels or labels == ['']:
|
||||
raise IDNAError('Empty domain')
|
||||
if not labels[-1]:
|
||||
del labels[-1]
|
||||
trailing_dot = True
|
||||
for label in labels:
|
||||
result.append(ulabel(label))
|
||||
s = ulabel(label)
|
||||
if s:
|
||||
result.append(s)
|
||||
else:
|
||||
raise IDNAError('Empty label')
|
||||
if trailing_dot:
|
||||
result.append(u'')
|
||||
return u'.'.join(result)
|
||||
|
||||
+353
-45
@@ -1,11 +1,12 @@
|
||||
# This file is automatically generated by tools/idna-data
|
||||
|
||||
__version__ = "6.3.0"
|
||||
__version__ = "10.0.0"
|
||||
scripts = {
|
||||
'Greek': (
|
||||
0x37000000374,
|
||||
0x37500000378,
|
||||
0x37a0000037e,
|
||||
0x37f00000380,
|
||||
0x38400000385,
|
||||
0x38600000387,
|
||||
0x3880000038b,
|
||||
@@ -34,7 +35,9 @@ scripts = {
|
||||
0x1ff200001ff5,
|
||||
0x1ff600001fff,
|
||||
0x212600002127,
|
||||
0x101400001018b,
|
||||
0xab650000ab66,
|
||||
0x101400001018f,
|
||||
0x101a0000101a1,
|
||||
0x1d2000001d246,
|
||||
),
|
||||
'Han': (
|
||||
@@ -46,12 +49,14 @@ scripts = {
|
||||
0x30210000302a,
|
||||
0x30380000303c,
|
||||
0x340000004db6,
|
||||
0x4e0000009fcd,
|
||||
0x4e0000009feb,
|
||||
0xf9000000fa6e,
|
||||
0xfa700000fada,
|
||||
0x200000002a6d7,
|
||||
0x2a7000002b735,
|
||||
0x2b7400002b81e,
|
||||
0x2b8200002cea2,
|
||||
0x2ceb00002ebe1,
|
||||
0x2f8000002fa1e,
|
||||
),
|
||||
'Hebrew': (
|
||||
@@ -68,7 +73,7 @@ scripts = {
|
||||
'Hiragana': (
|
||||
0x304100003097,
|
||||
0x309d000030a0,
|
||||
0x1b0010001b002,
|
||||
0x1b0010001b11f,
|
||||
0x1f2000001f201,
|
||||
),
|
||||
'Katakana': (
|
||||
@@ -88,6 +93,7 @@ joining_types = {
|
||||
0x602: 85,
|
||||
0x603: 85,
|
||||
0x604: 85,
|
||||
0x605: 85,
|
||||
0x608: 85,
|
||||
0x60b: 85,
|
||||
0x620: 68,
|
||||
@@ -365,7 +371,7 @@ joining_types = {
|
||||
0x844: 68,
|
||||
0x845: 68,
|
||||
0x846: 82,
|
||||
0x847: 68,
|
||||
0x847: 82,
|
||||
0x848: 68,
|
||||
0x849: 82,
|
||||
0x84a: 68,
|
||||
@@ -373,7 +379,7 @@ joining_types = {
|
||||
0x84c: 68,
|
||||
0x84d: 68,
|
||||
0x84e: 68,
|
||||
0x84f: 82,
|
||||
0x84f: 68,
|
||||
0x850: 68,
|
||||
0x851: 68,
|
||||
0x852: 68,
|
||||
@@ -383,7 +389,19 @@ joining_types = {
|
||||
0x856: 85,
|
||||
0x857: 85,
|
||||
0x858: 85,
|
||||
0x860: 68,
|
||||
0x861: 85,
|
||||
0x862: 68,
|
||||
0x863: 68,
|
||||
0x864: 68,
|
||||
0x865: 68,
|
||||
0x866: 85,
|
||||
0x867: 82,
|
||||
0x868: 68,
|
||||
0x869: 82,
|
||||
0x86a: 82,
|
||||
0x8a0: 68,
|
||||
0x8a1: 68,
|
||||
0x8a2: 68,
|
||||
0x8a3: 68,
|
||||
0x8a4: 68,
|
||||
@@ -395,6 +413,23 @@ joining_types = {
|
||||
0x8aa: 82,
|
||||
0x8ab: 82,
|
||||
0x8ac: 82,
|
||||
0x8ad: 85,
|
||||
0x8ae: 82,
|
||||
0x8af: 68,
|
||||
0x8b0: 68,
|
||||
0x8b1: 82,
|
||||
0x8b2: 82,
|
||||
0x8b3: 68,
|
||||
0x8b4: 68,
|
||||
0x8b6: 68,
|
||||
0x8b7: 68,
|
||||
0x8b8: 68,
|
||||
0x8b9: 82,
|
||||
0x8ba: 68,
|
||||
0x8bb: 68,
|
||||
0x8bc: 68,
|
||||
0x8bd: 68,
|
||||
0x8e2: 85,
|
||||
0x1806: 85,
|
||||
0x1807: 68,
|
||||
0x180a: 67,
|
||||
@@ -492,8 +527,8 @@ joining_types = {
|
||||
0x1882: 85,
|
||||
0x1883: 85,
|
||||
0x1884: 85,
|
||||
0x1885: 85,
|
||||
0x1886: 85,
|
||||
0x1885: 84,
|
||||
0x1886: 84,
|
||||
0x1887: 68,
|
||||
0x1888: 68,
|
||||
0x1889: 68,
|
||||
@@ -531,6 +566,7 @@ joining_types = {
|
||||
0x18aa: 68,
|
||||
0x200c: 85,
|
||||
0x200d: 67,
|
||||
0x202f: 85,
|
||||
0x2066: 85,
|
||||
0x2067: 85,
|
||||
0x2068: 85,
|
||||
@@ -587,6 +623,141 @@ joining_types = {
|
||||
0xa871: 68,
|
||||
0xa872: 76,
|
||||
0xa873: 85,
|
||||
0x10ac0: 68,
|
||||
0x10ac1: 68,
|
||||
0x10ac2: 68,
|
||||
0x10ac3: 68,
|
||||
0x10ac4: 68,
|
||||
0x10ac5: 82,
|
||||
0x10ac6: 85,
|
||||
0x10ac7: 82,
|
||||
0x10ac8: 85,
|
||||
0x10ac9: 82,
|
||||
0x10aca: 82,
|
||||
0x10acb: 85,
|
||||
0x10acc: 85,
|
||||
0x10acd: 76,
|
||||
0x10ace: 82,
|
||||
0x10acf: 82,
|
||||
0x10ad0: 82,
|
||||
0x10ad1: 82,
|
||||
0x10ad2: 82,
|
||||
0x10ad3: 68,
|
||||
0x10ad4: 68,
|
||||
0x10ad5: 68,
|
||||
0x10ad6: 68,
|
||||
0x10ad7: 76,
|
||||
0x10ad8: 68,
|
||||
0x10ad9: 68,
|
||||
0x10ada: 68,
|
||||
0x10adb: 68,
|
||||
0x10adc: 68,
|
||||
0x10add: 82,
|
||||
0x10ade: 68,
|
||||
0x10adf: 68,
|
||||
0x10ae0: 68,
|
||||
0x10ae1: 82,
|
||||
0x10ae2: 85,
|
||||
0x10ae3: 85,
|
||||
0x10ae4: 82,
|
||||
0x10aeb: 68,
|
||||
0x10aec: 68,
|
||||
0x10aed: 68,
|
||||
0x10aee: 68,
|
||||
0x10aef: 82,
|
||||
0x10b80: 68,
|
||||
0x10b81: 82,
|
||||
0x10b82: 68,
|
||||
0x10b83: 82,
|
||||
0x10b84: 82,
|
||||
0x10b85: 82,
|
||||
0x10b86: 68,
|
||||
0x10b87: 68,
|
||||
0x10b88: 68,
|
||||
0x10b89: 82,
|
||||
0x10b8a: 68,
|
||||
0x10b8b: 68,
|
||||
0x10b8c: 82,
|
||||
0x10b8d: 68,
|
||||
0x10b8e: 82,
|
||||
0x10b8f: 82,
|
||||
0x10b90: 68,
|
||||
0x10b91: 82,
|
||||
0x10ba9: 82,
|
||||
0x10baa: 82,
|
||||
0x10bab: 82,
|
||||
0x10bac: 82,
|
||||
0x10bad: 68,
|
||||
0x10bae: 68,
|
||||
0x10baf: 85,
|
||||
0x1e900: 68,
|
||||
0x1e901: 68,
|
||||
0x1e902: 68,
|
||||
0x1e903: 68,
|
||||
0x1e904: 68,
|
||||
0x1e905: 68,
|
||||
0x1e906: 68,
|
||||
0x1e907: 68,
|
||||
0x1e908: 68,
|
||||
0x1e909: 68,
|
||||
0x1e90a: 68,
|
||||
0x1e90b: 68,
|
||||
0x1e90c: 68,
|
||||
0x1e90d: 68,
|
||||
0x1e90e: 68,
|
||||
0x1e90f: 68,
|
||||
0x1e910: 68,
|
||||
0x1e911: 68,
|
||||
0x1e912: 68,
|
||||
0x1e913: 68,
|
||||
0x1e914: 68,
|
||||
0x1e915: 68,
|
||||
0x1e916: 68,
|
||||
0x1e917: 68,
|
||||
0x1e918: 68,
|
||||
0x1e919: 68,
|
||||
0x1e91a: 68,
|
||||
0x1e91b: 68,
|
||||
0x1e91c: 68,
|
||||
0x1e91d: 68,
|
||||
0x1e91e: 68,
|
||||
0x1e91f: 68,
|
||||
0x1e920: 68,
|
||||
0x1e921: 68,
|
||||
0x1e922: 68,
|
||||
0x1e923: 68,
|
||||
0x1e924: 68,
|
||||
0x1e925: 68,
|
||||
0x1e926: 68,
|
||||
0x1e927: 68,
|
||||
0x1e928: 68,
|
||||
0x1e929: 68,
|
||||
0x1e92a: 68,
|
||||
0x1e92b: 68,
|
||||
0x1e92c: 68,
|
||||
0x1e92d: 68,
|
||||
0x1e92e: 68,
|
||||
0x1e92f: 68,
|
||||
0x1e930: 68,
|
||||
0x1e931: 68,
|
||||
0x1e932: 68,
|
||||
0x1e933: 68,
|
||||
0x1e934: 68,
|
||||
0x1e935: 68,
|
||||
0x1e936: 68,
|
||||
0x1e937: 68,
|
||||
0x1e938: 68,
|
||||
0x1e939: 68,
|
||||
0x1e93a: 68,
|
||||
0x1e93b: 68,
|
||||
0x1e93c: 68,
|
||||
0x1e93d: 68,
|
||||
0x1e93e: 68,
|
||||
0x1e93f: 68,
|
||||
0x1e940: 68,
|
||||
0x1e941: 68,
|
||||
0x1e942: 68,
|
||||
0x1e943: 68,
|
||||
}
|
||||
codepoint_classes = {
|
||||
'PVALID': (
|
||||
@@ -858,6 +1029,10 @@ codepoint_classes = {
|
||||
0x52300000524,
|
||||
0x52500000526,
|
||||
0x52700000528,
|
||||
0x5290000052a,
|
||||
0x52b0000052c,
|
||||
0x52d0000052e,
|
||||
0x52f00000530,
|
||||
0x5590000055a,
|
||||
0x56100000587,
|
||||
0x591000005be,
|
||||
@@ -881,15 +1056,14 @@ codepoint_classes = {
|
||||
0x7c0000007f6,
|
||||
0x8000000082e,
|
||||
0x8400000085c,
|
||||
0x8a0000008a1,
|
||||
0x8a2000008ad,
|
||||
0x8e4000008ff,
|
||||
0x90000000958,
|
||||
0x8600000086b,
|
||||
0x8a0000008b5,
|
||||
0x8b6000008be,
|
||||
0x8d4000008e2,
|
||||
0x8e300000958,
|
||||
0x96000000964,
|
||||
0x96600000970,
|
||||
0x97100000978,
|
||||
0x97900000980,
|
||||
0x98100000984,
|
||||
0x97100000984,
|
||||
0x9850000098d,
|
||||
0x98f00000991,
|
||||
0x993000009a9,
|
||||
@@ -902,6 +1076,7 @@ codepoint_classes = {
|
||||
0x9d7000009d8,
|
||||
0x9e0000009e4,
|
||||
0x9e6000009f2,
|
||||
0x9fc000009fd,
|
||||
0xa0100000a04,
|
||||
0xa0500000a0b,
|
||||
0xa0f00000a11,
|
||||
@@ -930,6 +1105,7 @@ codepoint_classes = {
|
||||
0xad000000ad1,
|
||||
0xae000000ae4,
|
||||
0xae600000af0,
|
||||
0xaf900000b00,
|
||||
0xb0100000b04,
|
||||
0xb0500000b0d,
|
||||
0xb0f00000b11,
|
||||
@@ -960,20 +1136,19 @@ codepoint_classes = {
|
||||
0xbd000000bd1,
|
||||
0xbd700000bd8,
|
||||
0xbe600000bf0,
|
||||
0xc0100000c04,
|
||||
0xc0000000c04,
|
||||
0xc0500000c0d,
|
||||
0xc0e00000c11,
|
||||
0xc1200000c29,
|
||||
0xc2a00000c34,
|
||||
0xc3500000c3a,
|
||||
0xc2a00000c3a,
|
||||
0xc3d00000c45,
|
||||
0xc4600000c49,
|
||||
0xc4a00000c4e,
|
||||
0xc5500000c57,
|
||||
0xc5800000c5a,
|
||||
0xc5800000c5b,
|
||||
0xc6000000c64,
|
||||
0xc6600000c70,
|
||||
0xc8200000c84,
|
||||
0xc8000000c84,
|
||||
0xc8500000c8d,
|
||||
0xc8e00000c91,
|
||||
0xc9200000ca9,
|
||||
@@ -987,15 +1162,14 @@ codepoint_classes = {
|
||||
0xce000000ce4,
|
||||
0xce600000cf0,
|
||||
0xcf100000cf3,
|
||||
0xd0200000d04,
|
||||
0xd0000000d04,
|
||||
0xd0500000d0d,
|
||||
0xd0e00000d11,
|
||||
0xd1200000d3b,
|
||||
0xd3d00000d45,
|
||||
0xd1200000d45,
|
||||
0xd4600000d49,
|
||||
0xd4a00000d4f,
|
||||
0xd5700000d58,
|
||||
0xd6000000d64,
|
||||
0xd5400000d58,
|
||||
0xd5f00000d64,
|
||||
0xd6600000d70,
|
||||
0xd7a00000d80,
|
||||
0xd8200000d84,
|
||||
@@ -1008,6 +1182,7 @@ codepoint_classes = {
|
||||
0xdcf00000dd5,
|
||||
0xdd600000dd7,
|
||||
0xdd800000de0,
|
||||
0xde600000df0,
|
||||
0xdf200000df4,
|
||||
0xe0100000e33,
|
||||
0xe3400000e3b,
|
||||
@@ -1082,11 +1257,12 @@ codepoint_classes = {
|
||||
0x13180000135b,
|
||||
0x135d00001360,
|
||||
0x138000001390,
|
||||
0x13a0000013f5,
|
||||
0x13a0000013f6,
|
||||
0x14010000166d,
|
||||
0x166f00001680,
|
||||
0x16810000169b,
|
||||
0x16a0000016eb,
|
||||
0x16f1000016f9,
|
||||
0x17000000170d,
|
||||
0x170e00001715,
|
||||
0x172000001735,
|
||||
@@ -1103,7 +1279,7 @@ codepoint_classes = {
|
||||
0x182000001878,
|
||||
0x1880000018ab,
|
||||
0x18b0000018f6,
|
||||
0x19000000191d,
|
||||
0x19000000191f,
|
||||
0x19200000192c,
|
||||
0x19300000193c,
|
||||
0x19460000196e,
|
||||
@@ -1117,6 +1293,7 @@ codepoint_classes = {
|
||||
0x1a7f00001a8a,
|
||||
0x1a9000001a9a,
|
||||
0x1aa700001aa8,
|
||||
0x1ab000001abe,
|
||||
0x1b0000001b4c,
|
||||
0x1b5000001b5a,
|
||||
0x1b6b00001b74,
|
||||
@@ -1125,15 +1302,15 @@ codepoint_classes = {
|
||||
0x1c4000001c4a,
|
||||
0x1c4d00001c7e,
|
||||
0x1cd000001cd3,
|
||||
0x1cd400001cf7,
|
||||
0x1cd400001cfa,
|
||||
0x1d0000001d2c,
|
||||
0x1d2f00001d30,
|
||||
0x1d3b00001d3c,
|
||||
0x1d4e00001d4f,
|
||||
0x1d6b00001d78,
|
||||
0x1d7900001d9b,
|
||||
0x1dc000001de7,
|
||||
0x1dfc00001e00,
|
||||
0x1dc000001dfa,
|
||||
0x1dfb00001e00,
|
||||
0x1e0100001e02,
|
||||
0x1e0300001e04,
|
||||
0x1e0500001e06,
|
||||
@@ -1367,11 +1544,11 @@ codepoint_classes = {
|
||||
0x309d0000309f,
|
||||
0x30a1000030fb,
|
||||
0x30fc000030ff,
|
||||
0x31050000312e,
|
||||
0x31050000312f,
|
||||
0x31a0000031bb,
|
||||
0x31f000003200,
|
||||
0x340000004db6,
|
||||
0x4e0000009fcd,
|
||||
0x4e0000009feb,
|
||||
0xa0000000a48d,
|
||||
0xa4d00000a4fe,
|
||||
0xa5000000a60d,
|
||||
@@ -1413,7 +1590,9 @@ codepoint_classes = {
|
||||
0xa6930000a694,
|
||||
0xa6950000a696,
|
||||
0xa6970000a698,
|
||||
0xa69f0000a6e6,
|
||||
0xa6990000a69a,
|
||||
0xa69b0000a69c,
|
||||
0xa69e0000a6e6,
|
||||
0xa6f00000a6f2,
|
||||
0xa7170000a720,
|
||||
0xa7230000a724,
|
||||
@@ -1463,30 +1642,39 @@ codepoint_classes = {
|
||||
0xa7850000a786,
|
||||
0xa7870000a789,
|
||||
0xa78c0000a78d,
|
||||
0xa78e0000a78f,
|
||||
0xa78e0000a790,
|
||||
0xa7910000a792,
|
||||
0xa7930000a794,
|
||||
0xa7930000a796,
|
||||
0xa7970000a798,
|
||||
0xa7990000a79a,
|
||||
0xa79b0000a79c,
|
||||
0xa79d0000a79e,
|
||||
0xa79f0000a7a0,
|
||||
0xa7a10000a7a2,
|
||||
0xa7a30000a7a4,
|
||||
0xa7a50000a7a6,
|
||||
0xa7a70000a7a8,
|
||||
0xa7a90000a7aa,
|
||||
0xa7b50000a7b6,
|
||||
0xa7b70000a7b8,
|
||||
0xa7f70000a7f8,
|
||||
0xa7fa0000a828,
|
||||
0xa8400000a874,
|
||||
0xa8800000a8c5,
|
||||
0xa8800000a8c6,
|
||||
0xa8d00000a8da,
|
||||
0xa8e00000a8f8,
|
||||
0xa8fb0000a8fc,
|
||||
0xa8fd0000a8fe,
|
||||
0xa9000000a92e,
|
||||
0xa9300000a954,
|
||||
0xa9800000a9c1,
|
||||
0xa9cf0000a9da,
|
||||
0xa9e00000a9ff,
|
||||
0xaa000000aa37,
|
||||
0xaa400000aa4e,
|
||||
0xaa500000aa5a,
|
||||
0xaa600000aa77,
|
||||
0xaa7a0000aa7c,
|
||||
0xaa800000aac3,
|
||||
0xaa7a0000aac3,
|
||||
0xaadb0000aade,
|
||||
0xaae00000aaf0,
|
||||
0xaaf20000aaf7,
|
||||
@@ -1495,6 +1683,8 @@ codepoint_classes = {
|
||||
0xab110000ab17,
|
||||
0xab200000ab27,
|
||||
0xab280000ab2f,
|
||||
0xab300000ab5b,
|
||||
0xab600000ab66,
|
||||
0xabc00000abeb,
|
||||
0xabec0000abee,
|
||||
0xabf00000abfa,
|
||||
@@ -1507,7 +1697,7 @@ codepoint_classes = {
|
||||
0xfa230000fa25,
|
||||
0xfa270000fa2a,
|
||||
0xfb1e0000fb1f,
|
||||
0xfe200000fe27,
|
||||
0xfe200000fe30,
|
||||
0xfe730000fe74,
|
||||
0x100000001000c,
|
||||
0x1000d00010027,
|
||||
@@ -1519,20 +1709,32 @@ codepoint_classes = {
|
||||
0x101fd000101fe,
|
||||
0x102800001029d,
|
||||
0x102a0000102d1,
|
||||
0x103000001031f,
|
||||
0x1033000010341,
|
||||
0x102e0000102e1,
|
||||
0x1030000010320,
|
||||
0x1032d00010341,
|
||||
0x103420001034a,
|
||||
0x103500001037b,
|
||||
0x103800001039e,
|
||||
0x103a0000103c4,
|
||||
0x103c8000103d0,
|
||||
0x104280001049e,
|
||||
0x104a0000104aa,
|
||||
0x104d8000104fc,
|
||||
0x1050000010528,
|
||||
0x1053000010564,
|
||||
0x1060000010737,
|
||||
0x1074000010756,
|
||||
0x1076000010768,
|
||||
0x1080000010806,
|
||||
0x1080800010809,
|
||||
0x1080a00010836,
|
||||
0x1083700010839,
|
||||
0x1083c0001083d,
|
||||
0x1083f00010856,
|
||||
0x1086000010877,
|
||||
0x108800001089f,
|
||||
0x108e0000108f3,
|
||||
0x108f4000108f6,
|
||||
0x1090000010916,
|
||||
0x109200001093a,
|
||||
0x10980000109b8,
|
||||
@@ -1545,31 +1747,137 @@ codepoint_classes = {
|
||||
0x10a3800010a3b,
|
||||
0x10a3f00010a40,
|
||||
0x10a6000010a7d,
|
||||
0x10a8000010a9d,
|
||||
0x10ac000010ac8,
|
||||
0x10ac900010ae7,
|
||||
0x10b0000010b36,
|
||||
0x10b4000010b56,
|
||||
0x10b6000010b73,
|
||||
0x10b8000010b92,
|
||||
0x10c0000010c49,
|
||||
0x10cc000010cf3,
|
||||
0x1100000011047,
|
||||
0x1106600011070,
|
||||
0x11080000110bb,
|
||||
0x1107f000110bb,
|
||||
0x110d0000110e9,
|
||||
0x110f0000110fa,
|
||||
0x1110000011135,
|
||||
0x1113600011140,
|
||||
0x1115000011174,
|
||||
0x1117600011177,
|
||||
0x11180000111c5,
|
||||
0x111d0000111da,
|
||||
0x111ca000111cd,
|
||||
0x111d0000111db,
|
||||
0x111dc000111dd,
|
||||
0x1120000011212,
|
||||
0x1121300011238,
|
||||
0x1123e0001123f,
|
||||
0x1128000011287,
|
||||
0x1128800011289,
|
||||
0x1128a0001128e,
|
||||
0x1128f0001129e,
|
||||
0x1129f000112a9,
|
||||
0x112b0000112eb,
|
||||
0x112f0000112fa,
|
||||
0x1130000011304,
|
||||
0x113050001130d,
|
||||
0x1130f00011311,
|
||||
0x1131300011329,
|
||||
0x1132a00011331,
|
||||
0x1133200011334,
|
||||
0x113350001133a,
|
||||
0x1133c00011345,
|
||||
0x1134700011349,
|
||||
0x1134b0001134e,
|
||||
0x1135000011351,
|
||||
0x1135700011358,
|
||||
0x1135d00011364,
|
||||
0x113660001136d,
|
||||
0x1137000011375,
|
||||
0x114000001144b,
|
||||
0x114500001145a,
|
||||
0x11480000114c6,
|
||||
0x114c7000114c8,
|
||||
0x114d0000114da,
|
||||
0x11580000115b6,
|
||||
0x115b8000115c1,
|
||||
0x115d8000115de,
|
||||
0x1160000011641,
|
||||
0x1164400011645,
|
||||
0x116500001165a,
|
||||
0x11680000116b8,
|
||||
0x116c0000116ca,
|
||||
0x120000001236f,
|
||||
0x117000001171a,
|
||||
0x1171d0001172c,
|
||||
0x117300001173a,
|
||||
0x118c0000118ea,
|
||||
0x118ff00011900,
|
||||
0x11a0000011a3f,
|
||||
0x11a4700011a48,
|
||||
0x11a5000011a84,
|
||||
0x11a8600011a9a,
|
||||
0x11ac000011af9,
|
||||
0x11c0000011c09,
|
||||
0x11c0a00011c37,
|
||||
0x11c3800011c41,
|
||||
0x11c5000011c5a,
|
||||
0x11c7200011c90,
|
||||
0x11c9200011ca8,
|
||||
0x11ca900011cb7,
|
||||
0x11d0000011d07,
|
||||
0x11d0800011d0a,
|
||||
0x11d0b00011d37,
|
||||
0x11d3a00011d3b,
|
||||
0x11d3c00011d3e,
|
||||
0x11d3f00011d48,
|
||||
0x11d5000011d5a,
|
||||
0x120000001239a,
|
||||
0x1248000012544,
|
||||
0x130000001342f,
|
||||
0x1440000014647,
|
||||
0x1680000016a39,
|
||||
0x16a4000016a5f,
|
||||
0x16a6000016a6a,
|
||||
0x16ad000016aee,
|
||||
0x16af000016af5,
|
||||
0x16b0000016b37,
|
||||
0x16b4000016b44,
|
||||
0x16b5000016b5a,
|
||||
0x16b6300016b78,
|
||||
0x16b7d00016b90,
|
||||
0x16f0000016f45,
|
||||
0x16f5000016f7f,
|
||||
0x16f8f00016fa0,
|
||||
0x1b0000001b002,
|
||||
0x16fe000016fe2,
|
||||
0x17000000187ed,
|
||||
0x1880000018af3,
|
||||
0x1b0000001b11f,
|
||||
0x1b1700001b2fc,
|
||||
0x1bc000001bc6b,
|
||||
0x1bc700001bc7d,
|
||||
0x1bc800001bc89,
|
||||
0x1bc900001bc9a,
|
||||
0x1bc9d0001bc9f,
|
||||
0x1da000001da37,
|
||||
0x1da3b0001da6d,
|
||||
0x1da750001da76,
|
||||
0x1da840001da85,
|
||||
0x1da9b0001daa0,
|
||||
0x1daa10001dab0,
|
||||
0x1e0000001e007,
|
||||
0x1e0080001e019,
|
||||
0x1e01b0001e022,
|
||||
0x1e0230001e025,
|
||||
0x1e0260001e02b,
|
||||
0x1e8000001e8c5,
|
||||
0x1e8d00001e8d7,
|
||||
0x1e9220001e94b,
|
||||
0x1e9500001e95a,
|
||||
0x200000002a6d7,
|
||||
0x2a7000002b735,
|
||||
0x2b7400002b81e,
|
||||
0x2b8200002cea2,
|
||||
0x2ceb00002ebe1,
|
||||
),
|
||||
'CONTEXTJ': (
|
||||
0x200c0000200e,
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
__version__ = '2.6'
|
||||
__version__ = '2.7'
|
||||
|
||||
|
||||
+912
-367
File diff suppressed because it is too large
Load Diff
Vendored
+3
-2
@@ -27,7 +27,7 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__version__ = '2.9.5'
|
||||
__version__ = '2.10'
|
||||
|
||||
# high level interface
|
||||
from jinja2.environment import Environment, Template
|
||||
@@ -48,7 +48,7 @@ from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
|
||||
# exceptions
|
||||
from jinja2.exceptions import TemplateError, UndefinedError, \
|
||||
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
|
||||
TemplateAssertionError
|
||||
TemplateAssertionError, TemplateRuntimeError
|
||||
|
||||
# decorators and public utilities
|
||||
from jinja2.filters import environmentfilter, contextfilter, \
|
||||
@@ -64,6 +64,7 @@ __all__ = [
|
||||
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
|
||||
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
||||
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
||||
'TemplateRuntimeError',
|
||||
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
||||
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
||||
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
# generated by scripts/generate_identifier_pattern.py
|
||||
pattern = '·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯'
|
||||
Vendored
-71
File diff suppressed because one or more lines are too long
Vendored
+8
-6
@@ -189,9 +189,9 @@ async def auto_aiter(iterable):
|
||||
|
||||
class AsyncLoopContext(LoopContextBase):
|
||||
|
||||
def __init__(self, async_iterator, after, length, recurse=None,
|
||||
def __init__(self, async_iterator, undefined, after, length, recurse=None,
|
||||
depth0=0):
|
||||
LoopContextBase.__init__(self, recurse, depth0)
|
||||
LoopContextBase.__init__(self, undefined, recurse, depth0)
|
||||
self._async_iterator = async_iterator
|
||||
self._after = after
|
||||
self._length = length
|
||||
@@ -221,15 +221,16 @@ class AsyncLoopContextIterator(object):
|
||||
ctx.index0 += 1
|
||||
if ctx._after is _last_iteration:
|
||||
raise StopAsyncIteration()
|
||||
next_elem = ctx._after
|
||||
ctx._before = ctx._current
|
||||
ctx._current = ctx._after
|
||||
try:
|
||||
ctx._after = await ctx._async_iterator.__anext__()
|
||||
except StopAsyncIteration:
|
||||
ctx._after = _last_iteration
|
||||
return next_elem, ctx
|
||||
return ctx._current, ctx
|
||||
|
||||
|
||||
async def make_async_loop_context(iterable, recurse=None, depth0=0):
|
||||
async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0):
|
||||
# Length is more complicated and less efficient in async mode. The
|
||||
# reason for this is that we cannot know if length will be used
|
||||
# upfront but because length is a property we cannot lazily execute it
|
||||
@@ -251,4 +252,5 @@ async def make_async_loop_context(iterable, recurse=None, depth0=0):
|
||||
after = await async_iterator.__anext__()
|
||||
except StopAsyncIteration:
|
||||
after = _last_iteration
|
||||
return AsyncLoopContext(async_iterator, after, length, recurse, depth0)
|
||||
return AsyncLoopContext(async_iterator, undefined, after, length, recurse,
|
||||
depth0)
|
||||
|
||||
Vendored
+1
-1
@@ -297,7 +297,7 @@ class MemcachedBytecodeCache(BytecodeCache):
|
||||
Libraries compatible with this class:
|
||||
|
||||
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
|
||||
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
|
||||
- `python-memcached <https://www.tummy.com/Community/software/python-memcached/>`_
|
||||
- `cmemcache <http://gijsbert.org/cmemcache/>`_
|
||||
|
||||
(Unfortunately the django cache interface is not compatible because it
|
||||
|
||||
Vendored
+78
-10
@@ -130,9 +130,10 @@ class MacroRef(object):
|
||||
class Frame(object):
|
||||
"""Holds compile time information for us."""
|
||||
|
||||
def __init__(self, eval_ctx, parent=None):
|
||||
def __init__(self, eval_ctx, parent=None, level=None):
|
||||
self.eval_ctx = eval_ctx
|
||||
self.symbols = Symbols(parent and parent.symbols or None)
|
||||
self.symbols = Symbols(parent and parent.symbols or None,
|
||||
level=level)
|
||||
|
||||
# a toplevel frame is the root + soft frames such as if conditions.
|
||||
self.toplevel = False
|
||||
@@ -168,8 +169,10 @@ class Frame(object):
|
||||
rv.symbols = self.symbols.copy()
|
||||
return rv
|
||||
|
||||
def inner(self):
|
||||
def inner(self, isolated=False):
|
||||
"""Return an inner frame."""
|
||||
if isolated:
|
||||
return Frame(self.eval_ctx, level=self.symbols.level + 1)
|
||||
return Frame(self.eval_ctx, self)
|
||||
|
||||
def soft(self):
|
||||
@@ -302,6 +305,9 @@ class CodeGenerator(NodeVisitor):
|
||||
# Tracks parameter definition blocks
|
||||
self._param_def_block = []
|
||||
|
||||
# Tracks the current context.
|
||||
self._context_reference_stack = ['context']
|
||||
|
||||
# -- Various compilation helpers
|
||||
|
||||
def fail(self, msg, lineno):
|
||||
@@ -472,8 +478,8 @@ class CodeGenerator(NodeVisitor):
|
||||
if action == VAR_LOAD_PARAMETER:
|
||||
pass
|
||||
elif action == VAR_LOAD_RESOLVE:
|
||||
self.writeline('%s = resolve(%r)' %
|
||||
(target, param))
|
||||
self.writeline('%s = %s(%r)' %
|
||||
(target, self.get_resolve_func(), param))
|
||||
elif action == VAR_LOAD_ALIAS:
|
||||
self.writeline('%s = %s' % (target, param))
|
||||
elif action == VAR_LOAD_UNDEFINED:
|
||||
@@ -625,6 +631,27 @@ class CodeGenerator(NodeVisitor):
|
||||
if self._param_def_block:
|
||||
self._param_def_block[-1].discard(target)
|
||||
|
||||
def push_context_reference(self, target):
|
||||
self._context_reference_stack.append(target)
|
||||
|
||||
def pop_context_reference(self):
|
||||
self._context_reference_stack.pop()
|
||||
|
||||
def get_context_ref(self):
|
||||
return self._context_reference_stack[-1]
|
||||
|
||||
def get_resolve_func(self):
|
||||
target = self._context_reference_stack[-1]
|
||||
if target == 'context':
|
||||
return 'resolve'
|
||||
return '%s.resolve' % target
|
||||
|
||||
def derive_context(self, frame):
|
||||
return '%s.derived(%s)' % (
|
||||
self.get_context_ref(),
|
||||
self.dump_local_context(frame),
|
||||
)
|
||||
|
||||
def parameter_is_undeclared(self, target):
|
||||
"""Checks if a given target is an undeclared parameter."""
|
||||
if not self._param_def_block:
|
||||
@@ -793,8 +820,11 @@ class CodeGenerator(NodeVisitor):
|
||||
self.writeline('if parent_template is None:')
|
||||
self.indent()
|
||||
level += 1
|
||||
context = node.scoped and (
|
||||
'context.derived(%s)' % self.dump_local_context(frame)) or 'context'
|
||||
|
||||
if node.scoped:
|
||||
context = self.derive_context(frame)
|
||||
else:
|
||||
context = self.get_context_ref()
|
||||
|
||||
if supports_yield_from and not self.environment.is_async and \
|
||||
frame.buffer is None:
|
||||
@@ -1082,9 +1112,9 @@ class CodeGenerator(NodeVisitor):
|
||||
self.write(')')
|
||||
|
||||
if node.recursive:
|
||||
self.write(', loop_render_func, depth):')
|
||||
self.write(', undefined, loop_render_func, depth):')
|
||||
else:
|
||||
self.write(extended_loop and '):' or ':')
|
||||
self.write(extended_loop and ', undefined):' or ':')
|
||||
|
||||
self.indent()
|
||||
self.enter_frame(loop_frame)
|
||||
@@ -1129,6 +1159,13 @@ class CodeGenerator(NodeVisitor):
|
||||
self.indent()
|
||||
self.blockvisit(node.body, if_frame)
|
||||
self.outdent()
|
||||
for elif_ in node.elif_:
|
||||
self.writeline('elif ', elif_)
|
||||
self.visit(elif_.test, if_frame)
|
||||
self.write(':')
|
||||
self.indent()
|
||||
self.blockvisit(elif_.body, if_frame)
|
||||
self.outdent()
|
||||
if node.else_:
|
||||
self.writeline('else:')
|
||||
self.indent()
|
||||
@@ -1348,7 +1385,12 @@ class CodeGenerator(NodeVisitor):
|
||||
self.newline(node)
|
||||
self.visit(node.target, frame)
|
||||
self.write(' = (Markup if context.eval_ctx.autoescape '
|
||||
'else identity)(concat(%s))' % block_frame.buffer)
|
||||
'else identity)(')
|
||||
if node.filter is not None:
|
||||
self.visit_Filter(node.filter, block_frame)
|
||||
else:
|
||||
self.write('concat(%s)' % block_frame.buffer)
|
||||
self.write(')')
|
||||
self.pop_assign_tracking(frame)
|
||||
self.leave_frame(block_frame)
|
||||
|
||||
@@ -1373,6 +1415,18 @@ class CodeGenerator(NodeVisitor):
|
||||
|
||||
self.write(ref)
|
||||
|
||||
def visit_NSRef(self, node, frame):
|
||||
# NSRefs can only be used to store values; since they use the normal
|
||||
# `foo.bar` notation they will be parsed as a normal attribute access
|
||||
# when used anywhere but in a `set` context
|
||||
ref = frame.symbols.ref(node.name)
|
||||
self.writeline('if not isinstance(%s, Namespace):' % ref)
|
||||
self.indent()
|
||||
self.writeline('raise TemplateRuntimeError(%r)' %
|
||||
'cannot assign attribute on non-namespace object')
|
||||
self.outdent()
|
||||
self.writeline('%s[%r]' % (ref, node.attr))
|
||||
|
||||
def visit_Const(self, node, frame):
|
||||
val = node.as_const(frame.eval_ctx)
|
||||
if isinstance(val, float):
|
||||
@@ -1631,6 +1685,20 @@ class CodeGenerator(NodeVisitor):
|
||||
self.blockvisit(node.body, scope_frame)
|
||||
self.leave_frame(scope_frame)
|
||||
|
||||
def visit_OverlayScope(self, node, frame):
|
||||
ctx = self.temporary_identifier()
|
||||
self.writeline('%s = %s' % (ctx, self.derive_context(frame)))
|
||||
self.writeline('%s.vars = ' % ctx)
|
||||
self.visit(node.context, frame)
|
||||
self.push_context_reference(ctx)
|
||||
|
||||
scope_frame = frame.inner(isolated=True)
|
||||
scope_frame.symbols.analyze_node(node)
|
||||
self.enter_frame(scope_frame)
|
||||
self.blockvisit(node.body, scope_frame)
|
||||
self.leave_frame(scope_frame)
|
||||
self.pop_context_reference()
|
||||
|
||||
def visit_EvalContextModifier(self, node, frame):
|
||||
for keyword in node.options:
|
||||
self.writeline('context.eval_ctx.%s = ' % keyword.key)
|
||||
|
||||
Vendored
+1
-1
@@ -198,7 +198,7 @@ def translate_exception(exc_info, initial_skip=0):
|
||||
def get_jinja_locals(real_locals):
|
||||
ctx = real_locals.get('context')
|
||||
if ctx:
|
||||
locals = ctx.get_all()
|
||||
locals = ctx.get_all().copy()
|
||||
else:
|
||||
locals = {}
|
||||
|
||||
|
||||
Vendored
+4
-2
@@ -9,7 +9,7 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja2._compat import range_type
|
||||
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
|
||||
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner, Namespace
|
||||
|
||||
|
||||
# defaults for the parser / lexer
|
||||
@@ -35,7 +35,8 @@ DEFAULT_NAMESPACE = {
|
||||
'dict': dict,
|
||||
'lipsum': generate_lorem_ipsum,
|
||||
'cycler': Cycler,
|
||||
'joiner': Joiner
|
||||
'joiner': Joiner,
|
||||
'namespace': Namespace
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +48,7 @@ DEFAULT_POLICIES = {
|
||||
'truncate.leeway': 5,
|
||||
'json.dumps_function': None,
|
||||
'json.dumps_kwargs': {'sort_keys': True},
|
||||
'ext.i18n.trimmed': False,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -809,7 +809,7 @@ class Environment(object):
|
||||
@internalcode
|
||||
def get_template(self, name, parent=None, globals=None):
|
||||
"""Load a template from the loader. If a loader is configured this
|
||||
method ask the loader for the template and returns a :class:`Template`.
|
||||
method asks the loader for the template and returns a :class:`Template`.
|
||||
If the `parent` parameter is not `None`, :meth:`join_path` is called
|
||||
to get the real template name before loading.
|
||||
|
||||
|
||||
Vendored
+19
-1
@@ -10,6 +10,8 @@
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
import re
|
||||
|
||||
from jinja2 import nodes
|
||||
from jinja2.defaults import BLOCK_START_STRING, \
|
||||
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
|
||||
@@ -223,6 +225,7 @@ class InternationalizationExtension(Extension):
|
||||
plural_expr = None
|
||||
plural_expr_assignment = None
|
||||
variables = {}
|
||||
trimmed = None
|
||||
while parser.stream.current.type != 'block_end':
|
||||
if variables:
|
||||
parser.stream.expect('comma')
|
||||
@@ -241,6 +244,9 @@ class InternationalizationExtension(Extension):
|
||||
if parser.stream.current.type == 'assign':
|
||||
next(parser.stream)
|
||||
variables[name.value] = var = parser.parse_expression()
|
||||
elif trimmed is None and name.value in ('trimmed', 'notrimmed'):
|
||||
trimmed = name.value == 'trimmed'
|
||||
continue
|
||||
else:
|
||||
variables[name.value] = var = nodes.Name(name.value, 'load')
|
||||
|
||||
@@ -256,7 +262,7 @@ class InternationalizationExtension(Extension):
|
||||
|
||||
parser.stream.expect('block_end')
|
||||
|
||||
plural = plural_names = None
|
||||
plural = None
|
||||
have_plural = False
|
||||
referenced = set()
|
||||
|
||||
@@ -297,6 +303,13 @@ class InternationalizationExtension(Extension):
|
||||
elif plural_expr is None:
|
||||
parser.fail('pluralize without variables', lineno)
|
||||
|
||||
if trimmed is None:
|
||||
trimmed = self.environment.policies['ext.i18n.trimmed']
|
||||
if trimmed:
|
||||
singular = self._trim_whitespace(singular)
|
||||
if plural:
|
||||
plural = self._trim_whitespace(plural)
|
||||
|
||||
node = self._make_node(singular, plural, variables, plural_expr,
|
||||
bool(referenced),
|
||||
num_called_num and have_plural)
|
||||
@@ -306,6 +319,9 @@ class InternationalizationExtension(Extension):
|
||||
else:
|
||||
return node
|
||||
|
||||
def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')):
|
||||
return _ws_re.sub(' ', string.strip())
|
||||
|
||||
def _parse_block(self, parser, allow_pluralize):
|
||||
"""Parse until the next block tag with a given name."""
|
||||
referenced = []
|
||||
@@ -583,6 +599,8 @@ def babel_extract(fileobj, keywords, comment_tags, options):
|
||||
auto_reload=False
|
||||
)
|
||||
|
||||
if getbool(options, 'trimmed'):
|
||||
environment.policies['ext.i18n.trimmed'] = True
|
||||
if getbool(options, 'newstyle_gettext'):
|
||||
environment.newstyle_gettext = True
|
||||
|
||||
|
||||
Vendored
+160
-43
@@ -10,9 +10,10 @@
|
||||
"""
|
||||
import re
|
||||
import math
|
||||
import random
|
||||
import warnings
|
||||
|
||||
from random import choice
|
||||
from itertools import groupby
|
||||
from itertools import groupby, chain
|
||||
from collections import namedtuple
|
||||
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
|
||||
unicode_urlencode, htmlsafe_json_dumps
|
||||
@@ -52,22 +53,34 @@ def environmentfilter(f):
|
||||
return f
|
||||
|
||||
|
||||
def make_attrgetter(environment, attribute):
|
||||
def ignore_case(value):
|
||||
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
|
||||
to lowercase and returns other types as-is."""
|
||||
return value.lower() if isinstance(value, string_types) else value
|
||||
|
||||
|
||||
def make_attrgetter(environment, attribute, postprocess=None):
|
||||
"""Returns a callable that looks up the given attribute from a
|
||||
passed object with the rules of the environment. Dots are allowed
|
||||
to access attributes of attributes. Integer parts in paths are
|
||||
looked up as integers.
|
||||
"""
|
||||
if not isinstance(attribute, string_types) \
|
||||
or ('.' not in attribute and not attribute.isdigit()):
|
||||
return lambda x: environment.getitem(x, attribute)
|
||||
attribute = attribute.split('.')
|
||||
if attribute is None:
|
||||
attribute = []
|
||||
elif isinstance(attribute, string_types):
|
||||
attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
|
||||
else:
|
||||
attribute = [attribute]
|
||||
|
||||
def attrgetter(item):
|
||||
for part in attribute:
|
||||
if part.isdigit():
|
||||
part = int(part)
|
||||
item = environment.getitem(item, part)
|
||||
|
||||
if postprocess is not None:
|
||||
item = postprocess(item)
|
||||
|
||||
return item
|
||||
|
||||
return attrgetter
|
||||
|
||||
|
||||
@@ -190,7 +203,7 @@ def do_title(s):
|
||||
if item])
|
||||
|
||||
|
||||
def do_dictsort(value, case_sensitive=False, by='key'):
|
||||
def do_dictsort(value, case_sensitive=False, by='key', reverse=False):
|
||||
"""Sort a dict and yield (key, value) pairs. Because python dicts are
|
||||
unsorted you may want to use this function to order them by either
|
||||
key or value:
|
||||
@@ -200,6 +213,9 @@ def do_dictsort(value, case_sensitive=False, by='key'):
|
||||
{% for item in mydict|dictsort %}
|
||||
sort the dict by key, case insensitive
|
||||
|
||||
{% for item in mydict|dictsort(reverse=true) %}
|
||||
sort the dict by key, case insensitive, reverse order
|
||||
|
||||
{% for item in mydict|dictsort(true) %}
|
||||
sort the dict by key, case sensitive
|
||||
|
||||
@@ -211,20 +227,25 @@ def do_dictsort(value, case_sensitive=False, by='key'):
|
||||
elif by == 'value':
|
||||
pos = 1
|
||||
else:
|
||||
raise FilterArgumentError('You can only sort by either '
|
||||
'"key" or "value"')
|
||||
raise FilterArgumentError(
|
||||
'You can only sort by either "key" or "value"'
|
||||
)
|
||||
|
||||
def sort_func(item):
|
||||
value = item[pos]
|
||||
if isinstance(value, string_types) and not case_sensitive:
|
||||
value = value.lower()
|
||||
|
||||
if not case_sensitive:
|
||||
value = ignore_case(value)
|
||||
|
||||
return value
|
||||
|
||||
return sorted(value.items(), key=sort_func)
|
||||
return sorted(value.items(), key=sort_func, reverse=reverse)
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_sort(environment, value, reverse=False, case_sensitive=False,
|
||||
attribute=None):
|
||||
def do_sort(
|
||||
environment, value, reverse=False, case_sensitive=False, attribute=None
|
||||
):
|
||||
"""Sort an iterable. Per default it sorts ascending, if you pass it
|
||||
true as first argument it will reverse the sorting.
|
||||
|
||||
@@ -250,18 +271,85 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
|
||||
.. versionchanged:: 2.6
|
||||
The `attribute` parameter was added.
|
||||
"""
|
||||
if not case_sensitive:
|
||||
def sort_func(item):
|
||||
if isinstance(item, string_types):
|
||||
item = item.lower()
|
||||
return item
|
||||
else:
|
||||
sort_func = None
|
||||
if attribute is not None:
|
||||
getter = make_attrgetter(environment, attribute)
|
||||
def sort_func(item, processor=sort_func or (lambda x: x)):
|
||||
return processor(getter(item))
|
||||
return sorted(value, key=sort_func, reverse=reverse)
|
||||
key_func = make_attrgetter(
|
||||
environment, attribute,
|
||||
postprocess=ignore_case if not case_sensitive else None
|
||||
)
|
||||
return sorted(value, key=key_func, reverse=reverse)
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_unique(environment, value, case_sensitive=False, attribute=None):
|
||||
"""Returns a list of unique items from the the given iterable.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
|
||||
-> ['foo', 'bar', 'foobar']
|
||||
|
||||
The unique items are yielded in the same order as their first occurrence in
|
||||
the iterable passed to the filter.
|
||||
|
||||
:param case_sensitive: Treat upper and lower case strings as distinct.
|
||||
:param attribute: Filter objects with unique values for this attribute.
|
||||
"""
|
||||
getter = make_attrgetter(
|
||||
environment, attribute,
|
||||
postprocess=ignore_case if not case_sensitive else None
|
||||
)
|
||||
seen = set()
|
||||
|
||||
for item in value:
|
||||
key = getter(item)
|
||||
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
yield item
|
||||
|
||||
|
||||
def _min_or_max(environment, value, func, case_sensitive, attribute):
|
||||
it = iter(value)
|
||||
|
||||
try:
|
||||
first = next(it)
|
||||
except StopIteration:
|
||||
return environment.undefined('No aggregated item, sequence was empty.')
|
||||
|
||||
key_func = make_attrgetter(
|
||||
environment, attribute,
|
||||
ignore_case if not case_sensitive else None
|
||||
)
|
||||
return func(chain([first], it), key=key_func)
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_min(environment, value, case_sensitive=False, attribute=None):
|
||||
"""Return the smallest item from the sequence.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ [1, 2, 3]|min }}
|
||||
-> 1
|
||||
|
||||
:param case_sensitive: Treat upper and lower case strings as distinct.
|
||||
:param attribute: Get the object with the max value of this attribute.
|
||||
"""
|
||||
return _min_or_max(environment, value, min, case_sensitive, attribute)
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_max(environment, value, case_sensitive=False, attribute=None):
|
||||
"""Return the largest item from the sequence.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ [1, 2, 3]|max }}
|
||||
-> 3
|
||||
|
||||
:param case_sensitive: Treat upper and lower case strings as distinct.
|
||||
:param attribute: Get the object with the max value of this attribute.
|
||||
"""
|
||||
return _min_or_max(environment, value, max, case_sensitive, attribute)
|
||||
|
||||
|
||||
def do_default(value, default_value=u'', boolean=False):
|
||||
@@ -359,13 +447,13 @@ def do_last(environment, seq):
|
||||
return environment.undefined('No last item, sequence was empty.')
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def do_random(environment, seq):
|
||||
@contextfilter
|
||||
def do_random(context, seq):
|
||||
"""Return a random item from the sequence."""
|
||||
try:
|
||||
return choice(seq)
|
||||
return random.choice(seq)
|
||||
except IndexError:
|
||||
return environment.undefined('No random item, sequence was empty.')
|
||||
return context.environment.undefined('No random item, sequence was empty.')
|
||||
|
||||
|
||||
def do_filesizeformat(value, binary=False):
|
||||
@@ -445,21 +533,44 @@ def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
|
||||
return rv
|
||||
|
||||
|
||||
def do_indent(s, width=4, indentfirst=False):
|
||||
"""Return a copy of the passed string, each line indented by
|
||||
4 spaces. The first line is not indented. If you want to
|
||||
change the number of spaces or indent the first line too
|
||||
you can pass additional parameters to the filter:
|
||||
def do_indent(
|
||||
s, width=4, first=False, blank=False, indentfirst=None
|
||||
):
|
||||
"""Return a copy of the string with each line indented by 4 spaces. The
|
||||
first line and blank lines are not indented by default.
|
||||
|
||||
.. sourcecode:: jinja
|
||||
:param width: Number of spaces to indent by.
|
||||
:param first: Don't skip indenting the first line.
|
||||
:param blank: Don't skip indenting empty lines.
|
||||
|
||||
{{ mytext|indent(2, true) }}
|
||||
indent by two spaces and indent the first line too.
|
||||
.. versionchanged:: 2.10
|
||||
Blank lines are not indented by default.
|
||||
|
||||
Rename the ``indentfirst`` argument to ``first``.
|
||||
"""
|
||||
if indentfirst is not None:
|
||||
warnings.warn(DeprecationWarning(
|
||||
'The "indentfirst" argument is renamed to "first".'
|
||||
), stacklevel=2)
|
||||
first = indentfirst
|
||||
|
||||
s += u'\n' # this quirk is necessary for splitlines method
|
||||
indention = u' ' * width
|
||||
rv = (u'\n' + indention).join(s.splitlines())
|
||||
if indentfirst:
|
||||
|
||||
if blank:
|
||||
rv = (u'\n' + indention).join(s.splitlines())
|
||||
else:
|
||||
lines = s.splitlines()
|
||||
rv = lines.pop(0)
|
||||
|
||||
if lines:
|
||||
rv += u'\n' + u'\n'.join(
|
||||
indention + line if line else line for line in lines
|
||||
)
|
||||
|
||||
if first:
|
||||
rv = indention + rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
@@ -865,6 +976,9 @@ def do_select(*args, **kwargs):
|
||||
|
||||
{{ numbers|select("odd") }}
|
||||
{{ numbers|select("odd") }}
|
||||
{{ numbers|select("divisibleby", 3) }}
|
||||
{{ numbers|select("lessthan", 42) }}
|
||||
{{ strings|select("equalto", "mystring") }}
|
||||
|
||||
.. versionadded:: 2.7
|
||||
"""
|
||||
@@ -1045,6 +1159,8 @@ FILTERS = {
|
||||
'list': do_list,
|
||||
'lower': do_lower,
|
||||
'map': do_map,
|
||||
'min': do_min,
|
||||
'max': do_max,
|
||||
'pprint': do_pprint,
|
||||
'random': do_random,
|
||||
'reject': do_reject,
|
||||
@@ -1063,6 +1179,7 @@ FILTERS = {
|
||||
'title': do_title,
|
||||
'trim': do_trim,
|
||||
'truncate': do_truncate,
|
||||
'unique': do_unique,
|
||||
'upper': do_upper,
|
||||
'urlencode': do_urlencode,
|
||||
'urlize': do_urlize,
|
||||
|
||||
Vendored
+19
-6
@@ -24,11 +24,13 @@ def symbols_for_node(node, parent_symbols=None):
|
||||
|
||||
class Symbols(object):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
if parent is None:
|
||||
self.level = 0
|
||||
else:
|
||||
self.level = parent.level + 1
|
||||
def __init__(self, parent=None, level=None):
|
||||
if level is None:
|
||||
if parent is None:
|
||||
level = 0
|
||||
else:
|
||||
level = parent.level + 1
|
||||
self.level = level
|
||||
self.parent = parent
|
||||
self.refs = {}
|
||||
self.loads = {}
|
||||
@@ -167,6 +169,10 @@ class RootVisitor(NodeVisitor):
|
||||
for child in node.iter_child_nodes(exclude=('call',)):
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_OverlayScope(self, node, **kwargs):
|
||||
for child in node.body:
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_For(self, node, for_branch='body', **kwargs):
|
||||
if for_branch == 'body':
|
||||
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||
@@ -209,6 +215,9 @@ class FrameSymbolVisitor(NodeVisitor):
|
||||
elif node.ctx == 'load':
|
||||
self.symbols.load(node.name)
|
||||
|
||||
def visit_NSRef(self, node, **kwargs):
|
||||
self.symbols.load(node.name)
|
||||
|
||||
def visit_If(self, node, **kwargs):
|
||||
self.visit(node.test, **kwargs)
|
||||
|
||||
@@ -222,9 +231,10 @@ class FrameSymbolVisitor(NodeVisitor):
|
||||
return rv
|
||||
|
||||
body_symbols = inner_visit(node.body)
|
||||
elif_symbols = inner_visit(node.elif_)
|
||||
else_symbols = inner_visit(node.else_ or ())
|
||||
|
||||
self.symbols.branch_update([body_symbols, else_symbols])
|
||||
self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
|
||||
|
||||
def visit_Macro(self, node, **kwargs):
|
||||
self.symbols.store(node.name)
|
||||
@@ -271,3 +281,6 @@ class FrameSymbolVisitor(NodeVisitor):
|
||||
|
||||
def visit_Block(self, node, **kwargs):
|
||||
"""Stop visiting at blocks."""
|
||||
|
||||
def visit_OverlayScope(self, node, **kwargs):
|
||||
"""Do not visit into overlay scopes."""
|
||||
|
||||
Vendored
+29
-27
@@ -15,14 +15,12 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
from operator import itemgetter
|
||||
from collections import deque
|
||||
from operator import itemgetter
|
||||
|
||||
from jinja2._compat import implements_iterator, intern, iteritems, text_type
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2.utils import LRUCache
|
||||
from jinja2._compat import iteritems, implements_iterator, text_type, intern
|
||||
|
||||
|
||||
# cache for the lexers. Exists in order to be able to have multiple
|
||||
# environments with the same lexer
|
||||
@@ -34,28 +32,25 @@ string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
||||
integer_re = re.compile(r'\d+')
|
||||
|
||||
def _make_name_re():
|
||||
try:
|
||||
compile('föö', '<unknown>', 'eval')
|
||||
except SyntaxError:
|
||||
return re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
|
||||
|
||||
try:
|
||||
# check if this Python supports Unicode identifiers
|
||||
compile('föö', '<unknown>', 'eval')
|
||||
except SyntaxError:
|
||||
# no Unicode support, use ASCII identifiers
|
||||
name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
|
||||
check_ident = False
|
||||
else:
|
||||
# Unicode support, build a pattern to match valid characters, and set flag
|
||||
# to use str.isidentifier to validate during lexing
|
||||
from jinja2 import _identifier
|
||||
name_re = re.compile(r'[\w{0}]+'.format(_identifier.pattern))
|
||||
check_ident = True
|
||||
# remove the pattern from memory after building the regex
|
||||
import sys
|
||||
del sys.modules['jinja2._identifier']
|
||||
import jinja2
|
||||
from jinja2 import _stringdefs
|
||||
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
|
||||
_stringdefs.xid_continue))
|
||||
|
||||
# Save some memory here
|
||||
sys.modules.pop('jinja2._stringdefs')
|
||||
del _stringdefs
|
||||
del jinja2._stringdefs
|
||||
|
||||
return name_re
|
||||
|
||||
# we use the unicode identifier rule if this python version is able
|
||||
# to handle unicode identifiers, otherwise the standard ASCII one.
|
||||
name_re = _make_name_re()
|
||||
del _make_name_re
|
||||
del jinja2._identifier
|
||||
del _identifier
|
||||
|
||||
float_re = re.compile(r'(?<!\.)\d+\.\d+')
|
||||
newline_re = re.compile(r'(\r\n|\r|\n)')
|
||||
@@ -352,7 +347,10 @@ class TokenStream(object):
|
||||
return self.next_if(expr) is not None
|
||||
|
||||
def __next__(self):
|
||||
"""Go one token ahead and return the old one"""
|
||||
"""Go one token ahead and return the old one.
|
||||
|
||||
Use the built-in :func:`next` instead of calling this directly.
|
||||
"""
|
||||
rv = self.current
|
||||
if self._pushed:
|
||||
self.current = self._pushed.popleft()
|
||||
@@ -577,6 +575,10 @@ class Lexer(object):
|
||||
token = value
|
||||
elif token == 'name':
|
||||
value = str(value)
|
||||
if check_ident and not value.isidentifier():
|
||||
raise TemplateSyntaxError(
|
||||
'Invalid character in identifier',
|
||||
lineno, name, filename)
|
||||
elif token == 'string':
|
||||
# try to unescape string
|
||||
try:
|
||||
|
||||
Vendored
+220
@@ -0,0 +1,220 @@
|
||||
import sys
|
||||
from ast import literal_eval
|
||||
from itertools import islice, chain
|
||||
from jinja2 import nodes
|
||||
from jinja2._compat import text_type
|
||||
from jinja2.compiler import CodeGenerator, has_safe_repr
|
||||
from jinja2.environment import Environment, Template
|
||||
from jinja2.utils import concat, escape
|
||||
|
||||
|
||||
def native_concat(nodes):
|
||||
"""Return a native Python type from the list of compiled nodes. If the
|
||||
result is a single node, its value is returned. Otherwise, the nodes are
|
||||
concatenated as strings. If the result can be parsed with
|
||||
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
|
||||
string is returned.
|
||||
"""
|
||||
head = list(islice(nodes, 2))
|
||||
|
||||
if not head:
|
||||
return None
|
||||
|
||||
if len(head) == 1:
|
||||
out = head[0]
|
||||
else:
|
||||
out = u''.join([text_type(v) for v in chain(head, nodes)])
|
||||
|
||||
try:
|
||||
return literal_eval(out)
|
||||
except (ValueError, SyntaxError, MemoryError):
|
||||
return out
|
||||
|
||||
|
||||
class NativeCodeGenerator(CodeGenerator):
|
||||
"""A code generator which avoids injecting ``to_string()`` calls around the
|
||||
internal code Jinja uses to render templates.
|
||||
"""
|
||||
|
||||
def visit_Output(self, node, frame):
|
||||
"""Same as :meth:`CodeGenerator.visit_Output`, but do not call
|
||||
``to_string`` on output nodes in generated code.
|
||||
"""
|
||||
if self.has_known_extends and frame.require_output_check:
|
||||
return
|
||||
|
||||
finalize = self.environment.finalize
|
||||
finalize_context = getattr(finalize, 'contextfunction', False)
|
||||
finalize_eval = getattr(finalize, 'evalcontextfunction', False)
|
||||
finalize_env = getattr(finalize, 'environmentfunction', False)
|
||||
|
||||
if finalize is not None:
|
||||
if finalize_context or finalize_eval:
|
||||
const_finalize = None
|
||||
elif finalize_env:
|
||||
def const_finalize(x):
|
||||
return finalize(self.environment, x)
|
||||
else:
|
||||
const_finalize = finalize
|
||||
else:
|
||||
def const_finalize(x):
|
||||
return x
|
||||
|
||||
# If we are inside a frame that requires output checking, we do so.
|
||||
outdent_later = False
|
||||
|
||||
if frame.require_output_check:
|
||||
self.writeline('if parent_template is None:')
|
||||
self.indent()
|
||||
outdent_later = True
|
||||
|
||||
# Try to evaluate as many chunks as possible into a static string at
|
||||
# compile time.
|
||||
body = []
|
||||
|
||||
for child in node.nodes:
|
||||
try:
|
||||
if const_finalize is None:
|
||||
raise nodes.Impossible()
|
||||
|
||||
const = child.as_const(frame.eval_ctx)
|
||||
if not has_safe_repr(const):
|
||||
raise nodes.Impossible()
|
||||
except nodes.Impossible:
|
||||
body.append(child)
|
||||
continue
|
||||
|
||||
# the frame can't be volatile here, because otherwise the as_const
|
||||
# function would raise an Impossible exception at that point
|
||||
try:
|
||||
if frame.eval_ctx.autoescape:
|
||||
if hasattr(const, '__html__'):
|
||||
const = const.__html__()
|
||||
else:
|
||||
const = escape(const)
|
||||
|
||||
const = const_finalize(const)
|
||||
except Exception:
|
||||
# if something goes wrong here we evaluate the node at runtime
|
||||
# for easier debugging
|
||||
body.append(child)
|
||||
continue
|
||||
|
||||
if body and isinstance(body[-1], list):
|
||||
body[-1].append(const)
|
||||
else:
|
||||
body.append([const])
|
||||
|
||||
# if we have less than 3 nodes or a buffer we yield or extend/append
|
||||
if len(body) < 3 or frame.buffer is not None:
|
||||
if frame.buffer is not None:
|
||||
# for one item we append, for more we extend
|
||||
if len(body) == 1:
|
||||
self.writeline('%s.append(' % frame.buffer)
|
||||
else:
|
||||
self.writeline('%s.extend((' % frame.buffer)
|
||||
|
||||
self.indent()
|
||||
|
||||
for item in body:
|
||||
if isinstance(item, list):
|
||||
val = repr(native_concat(item))
|
||||
|
||||
if frame.buffer is None:
|
||||
self.writeline('yield ' + val)
|
||||
else:
|
||||
self.writeline(val + ',')
|
||||
else:
|
||||
if frame.buffer is None:
|
||||
self.writeline('yield ', item)
|
||||
else:
|
||||
self.newline(item)
|
||||
|
||||
close = 0
|
||||
|
||||
if finalize is not None:
|
||||
self.write('environment.finalize(')
|
||||
|
||||
if finalize_context:
|
||||
self.write('context, ')
|
||||
|
||||
close += 1
|
||||
|
||||
self.visit(item, frame)
|
||||
|
||||
if close > 0:
|
||||
self.write(')' * close)
|
||||
|
||||
if frame.buffer is not None:
|
||||
self.write(',')
|
||||
|
||||
if frame.buffer is not None:
|
||||
# close the open parentheses
|
||||
self.outdent()
|
||||
self.writeline(len(body) == 1 and ')' or '))')
|
||||
|
||||
# otherwise we create a format string as this is faster in that case
|
||||
else:
|
||||
format = []
|
||||
arguments = []
|
||||
|
||||
for item in body:
|
||||
if isinstance(item, list):
|
||||
format.append(native_concat(item).replace('%', '%%'))
|
||||
else:
|
||||
format.append('%s')
|
||||
arguments.append(item)
|
||||
|
||||
self.writeline('yield ')
|
||||
self.write(repr(concat(format)) + ' % (')
|
||||
self.indent()
|
||||
|
||||
for argument in arguments:
|
||||
self.newline(argument)
|
||||
close = 0
|
||||
|
||||
if finalize is not None:
|
||||
self.write('environment.finalize(')
|
||||
|
||||
if finalize_context:
|
||||
self.write('context, ')
|
||||
elif finalize_eval:
|
||||
self.write('context.eval_ctx, ')
|
||||
elif finalize_env:
|
||||
self.write('environment, ')
|
||||
|
||||
close += 1
|
||||
|
||||
self.visit(argument, frame)
|
||||
self.write(')' * close + ', ')
|
||||
|
||||
self.outdent()
|
||||
self.writeline(')')
|
||||
|
||||
if outdent_later:
|
||||
self.outdent()
|
||||
|
||||
|
||||
class NativeTemplate(Template):
|
||||
def render(self, *args, **kwargs):
|
||||
"""Render the template to produce a native Python type. If the result
|
||||
is a single node, its value is returned. Otherwise, the nodes are
|
||||
concatenated as strings. If the result can be parsed with
|
||||
:func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
|
||||
string is returned.
|
||||
"""
|
||||
vars = dict(*args, **kwargs)
|
||||
|
||||
try:
|
||||
return native_concat(self.root_render_func(self.new_context(vars)))
|
||||
except Exception:
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
return self.environment.handle_exception(exc_info, True)
|
||||
|
||||
|
||||
class NativeEnvironment(Environment):
|
||||
"""An environment that renders templates to native Python types."""
|
||||
|
||||
code_generator_class = NativeCodeGenerator
|
||||
template_class = NativeTemplate
|
||||
Vendored
+77
-17
@@ -314,7 +314,7 @@ class For(Stmt):
|
||||
|
||||
class If(Stmt):
|
||||
"""If `test` is true, `body` is rendered, else `else_`."""
|
||||
fields = ('test', 'body', 'else_')
|
||||
fields = ('test', 'body', 'elif_', 'else_')
|
||||
|
||||
|
||||
class Macro(Stmt):
|
||||
@@ -387,7 +387,7 @@ class Assign(Stmt):
|
||||
|
||||
class AssignBlock(Stmt):
|
||||
"""Assigns a block to a target."""
|
||||
fields = ('target', 'body')
|
||||
fields = ('target', 'filter', 'body')
|
||||
|
||||
|
||||
class Expr(Node):
|
||||
@@ -465,6 +465,18 @@ class Name(Expr):
|
||||
'True', 'False', 'None')
|
||||
|
||||
|
||||
class NSRef(Expr):
|
||||
"""Reference to a namespace value assignment"""
|
||||
fields = ('name', 'attr')
|
||||
|
||||
def can_assign(self):
|
||||
# We don't need any special checks here; NSRef assignments have a
|
||||
# runtime check to ensure the target is a namespace object which will
|
||||
# have been checked already as it is created using a normal assignment
|
||||
# which goes through a `Name` node.
|
||||
return True
|
||||
|
||||
|
||||
class Literal(Expr):
|
||||
"""Baseclass for literals."""
|
||||
abstract = True
|
||||
@@ -587,6 +599,25 @@ class CondExpr(Expr):
|
||||
return self.expr2.as_const(eval_ctx)
|
||||
|
||||
|
||||
def args_as_const(node, eval_ctx):
|
||||
args = [x.as_const(eval_ctx) for x in node.args]
|
||||
kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
|
||||
|
||||
if node.dyn_args is not None:
|
||||
try:
|
||||
args.extend(node.dyn_args.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
if node.dyn_kwargs is not None:
|
||||
try:
|
||||
kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
return args, kwargs
|
||||
|
||||
|
||||
class Filter(Expr):
|
||||
"""This node applies a filter on an expression. `name` is the name of
|
||||
the filter, the rest of the fields are the same as for :class:`Call`.
|
||||
@@ -594,44 +625,41 @@ class Filter(Expr):
|
||||
If the `node` of a filter is `None` the contents of the last buffer are
|
||||
filtered. Buffers are created by macros and filter blocks.
|
||||
"""
|
||||
|
||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
|
||||
if eval_ctx.volatile or self.node is None:
|
||||
raise Impossible()
|
||||
|
||||
# we have to be careful here because we call filter_ below.
|
||||
# if this variable would be called filter, 2to3 would wrap the
|
||||
# call in a list beause it is assuming we are talking about the
|
||||
# builtin filter function here which no longer returns a list in
|
||||
# python 3. because of that, do not rename filter_ to filter!
|
||||
filter_ = self.environment.filters.get(self.name)
|
||||
|
||||
if filter_ is None or getattr(filter_, 'contextfilter', False):
|
||||
raise Impossible()
|
||||
|
||||
# We cannot constant handle async filters, so we need to make sure
|
||||
# to not go down this path.
|
||||
if eval_ctx.environment.is_async and \
|
||||
getattr(filter_, 'asyncfiltervariant', False):
|
||||
if (
|
||||
eval_ctx.environment.is_async
|
||||
and getattr(filter_, 'asyncfiltervariant', False)
|
||||
):
|
||||
raise Impossible()
|
||||
|
||||
obj = self.node.as_const(eval_ctx)
|
||||
args = [obj] + [x.as_const(eval_ctx) for x in self.args]
|
||||
args, kwargs = args_as_const(self, eval_ctx)
|
||||
args.insert(0, self.node.as_const(eval_ctx))
|
||||
|
||||
if getattr(filter_, 'evalcontextfilter', False):
|
||||
args.insert(0, eval_ctx)
|
||||
elif getattr(filter_, 'environmentfilter', False):
|
||||
args.insert(0, self.environment)
|
||||
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
|
||||
if self.dyn_args is not None:
|
||||
try:
|
||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
if self.dyn_kwargs is not None:
|
||||
try:
|
||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
try:
|
||||
return filter_(*args, **kwargs)
|
||||
except Exception:
|
||||
@@ -642,8 +670,24 @@ class Test(Expr):
|
||||
"""Applies a test on an expression. `name` is the name of the test, the
|
||||
rest of the fields are the same as for :class:`Call`.
|
||||
"""
|
||||
|
||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
test = self.environment.tests.get(self.name)
|
||||
|
||||
if test is None:
|
||||
raise Impossible()
|
||||
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
args, kwargs = args_as_const(self, eval_ctx)
|
||||
args.insert(0, self.node.as_const(eval_ctx))
|
||||
|
||||
try:
|
||||
return test(*args, **kwargs)
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
|
||||
class Call(Expr):
|
||||
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
||||
@@ -914,6 +958,22 @@ class Scope(Stmt):
|
||||
fields = ('body',)
|
||||
|
||||
|
||||
class OverlayScope(Stmt):
|
||||
"""An overlay scope for extensions. This is a largely unoptimized scope
|
||||
that however can be used to introduce completely arbitrary variables into
|
||||
a sub scope from a dictionary or dictionary like object. The `context`
|
||||
field has to evaluate to a dictionary object.
|
||||
|
||||
Example usage::
|
||||
|
||||
OverlayScope(context=self.call_method('get_context'),
|
||||
body=[...])
|
||||
|
||||
.. versionadded:: 2.10
|
||||
"""
|
||||
fields = ('context', 'body')
|
||||
|
||||
|
||||
class EvalContextModifier(Stmt):
|
||||
"""Modifies the eval context. For each option that should be modified,
|
||||
a :class:`Keyword` has to be added to the :attr:`options` list.
|
||||
|
||||
Vendored
+19
-14
@@ -176,13 +176,14 @@ class Parser(object):
|
||||
def parse_set(self):
|
||||
"""Parse an assign statement."""
|
||||
lineno = next(self.stream).lineno
|
||||
target = self.parse_assign_target()
|
||||
target = self.parse_assign_target(with_namespace=True)
|
||||
if self.stream.skip_if('assign'):
|
||||
expr = self.parse_tuple()
|
||||
return nodes.Assign(target, expr, lineno=lineno)
|
||||
filter_node = self.parse_filter(None)
|
||||
body = self.parse_statements(('name:endset',),
|
||||
drop_needle=True)
|
||||
return nodes.AssignBlock(target, body, lineno=lineno)
|
||||
return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
|
||||
|
||||
def parse_for(self):
|
||||
"""Parse a for loop."""
|
||||
@@ -210,17 +211,16 @@ class Parser(object):
|
||||
node.test = self.parse_tuple(with_condexpr=False)
|
||||
node.body = self.parse_statements(('name:elif', 'name:else',
|
||||
'name:endif'))
|
||||
node.elif_ = []
|
||||
node.else_ = []
|
||||
token = next(self.stream)
|
||||
if token.test('name:elif'):
|
||||
new_node = nodes.If(lineno=self.stream.current.lineno)
|
||||
node.else_ = [new_node]
|
||||
node = new_node
|
||||
node = nodes.If(lineno=self.stream.current.lineno)
|
||||
result.elif_.append(node)
|
||||
continue
|
||||
elif token.test('name:else'):
|
||||
node.else_ = self.parse_statements(('name:endif',),
|
||||
drop_needle=True)
|
||||
else:
|
||||
node.else_ = []
|
||||
result.else_ = self.parse_statements(('name:endif',),
|
||||
drop_needle=True)
|
||||
break
|
||||
return result
|
||||
|
||||
@@ -334,10 +334,9 @@ class Parser(object):
|
||||
if parse_context() or self.stream.current.type != 'comma':
|
||||
break
|
||||
else:
|
||||
break
|
||||
self.stream.expect('name')
|
||||
if not hasattr(node, 'with_context'):
|
||||
node.with_context = False
|
||||
self.stream.skip_if('comma')
|
||||
return node
|
||||
|
||||
def parse_signature(self, node):
|
||||
@@ -395,15 +394,21 @@ class Parser(object):
|
||||
return node
|
||||
|
||||
def parse_assign_target(self, with_tuple=True, name_only=False,
|
||||
extra_end_rules=None):
|
||||
extra_end_rules=None, with_namespace=False):
|
||||
"""Parse an assignment target. As Jinja2 allows assignments to
|
||||
tuples, this function can parse all allowed assignment targets. Per
|
||||
default assignments to tuples are parsed, that can be disable however
|
||||
by setting `with_tuple` to `False`. If only assignments to names are
|
||||
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
||||
parameter is forwarded to the tuple parsing function.
|
||||
parameter is forwarded to the tuple parsing function. If
|
||||
`with_namespace` is enabled, a namespace assignment may be parsed.
|
||||
"""
|
||||
if name_only:
|
||||
if with_namespace and self.stream.look().type == 'dot':
|
||||
token = self.stream.expect('name')
|
||||
next(self.stream) # dot
|
||||
attr = self.stream.expect('name')
|
||||
target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
|
||||
elif name_only:
|
||||
token = self.stream.expect('name')
|
||||
target = nodes.Name(token.value, 'store', lineno=token.lineno)
|
||||
else:
|
||||
|
||||
Vendored
+43
-15
@@ -11,9 +11,11 @@
|
||||
import sys
|
||||
|
||||
from itertools import chain
|
||||
from types import MethodType
|
||||
|
||||
from jinja2.nodes import EvalContext, _context_function_types
|
||||
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
|
||||
internalcode, object_type_repr, evalcontextfunction
|
||||
internalcode, object_type_repr, evalcontextfunction, Namespace
|
||||
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
||||
TemplateNotFound
|
||||
from jinja2._compat import imap, text_type, iteritems, \
|
||||
@@ -25,7 +27,7 @@ from jinja2._compat import imap, text_type, iteritems, \
|
||||
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
||||
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
||||
'markup_join', 'unicode_join', 'to_string', 'identity',
|
||||
'TemplateNotFound']
|
||||
'TemplateNotFound', 'Namespace']
|
||||
|
||||
#: the name of the function that is used to convert something into
|
||||
#: a string. We can just use the text type here.
|
||||
@@ -34,6 +36,7 @@ to_string = text_type
|
||||
#: the identity function. Useful for certain things in the environment
|
||||
identity = lambda x: x
|
||||
|
||||
_first_iteration = object()
|
||||
_last_iteration = object()
|
||||
|
||||
|
||||
@@ -167,7 +170,7 @@ class Context(with_metaclass(ContextMeta)):
|
||||
# In case we detect the fast resolve mode we can set up an alias
|
||||
# here that bypasses the legacy code logic.
|
||||
if self._fast_resolve_mode:
|
||||
self.resolve_or_missing = resolve_or_missing
|
||||
self.resolve_or_missing = MethodType(resolve_or_missing, self)
|
||||
|
||||
def super(self, name, current):
|
||||
"""Render a parent block."""
|
||||
@@ -239,13 +242,14 @@ class Context(with_metaclass(ContextMeta)):
|
||||
__traceback_hide__ = True # noqa
|
||||
|
||||
# Allow callable classes to take a context
|
||||
fn = __obj.__call__
|
||||
for fn_type in ('contextfunction',
|
||||
'evalcontextfunction',
|
||||
'environmentfunction'):
|
||||
if hasattr(fn, fn_type):
|
||||
__obj = fn
|
||||
break
|
||||
if hasattr(__obj, '__call__'):
|
||||
fn = __obj.__call__
|
||||
for fn_type in ('contextfunction',
|
||||
'evalcontextfunction',
|
||||
'environmentfunction'):
|
||||
if hasattr(fn, fn_type):
|
||||
__obj = fn
|
||||
break
|
||||
|
||||
if isinstance(__obj, _context_function_types):
|
||||
if getattr(__obj, 'contextfunction', 0):
|
||||
@@ -347,13 +351,17 @@ class BlockReference(object):
|
||||
class LoopContextBase(object):
|
||||
"""A loop context for dynamic iteration."""
|
||||
|
||||
_before = _first_iteration
|
||||
_current = _first_iteration
|
||||
_after = _last_iteration
|
||||
_length = None
|
||||
|
||||
def __init__(self, recurse=None, depth0=0):
|
||||
def __init__(self, undefined, recurse=None, depth0=0):
|
||||
self._undefined = undefined
|
||||
self._recurse = recurse
|
||||
self.index0 = -1
|
||||
self.depth0 = depth0
|
||||
self._last_checked_value = missing
|
||||
|
||||
def cycle(self, *args):
|
||||
"""Cycles among the arguments with the current loop index."""
|
||||
@@ -361,6 +369,13 @@ class LoopContextBase(object):
|
||||
raise TypeError('no items for cycling given')
|
||||
return args[self.index0 % len(args)]
|
||||
|
||||
def changed(self, *value):
|
||||
"""Checks whether the value has changed since the last call."""
|
||||
if self._last_checked_value != value:
|
||||
self._last_checked_value = value
|
||||
return True
|
||||
return False
|
||||
|
||||
first = property(lambda x: x.index0 == 0)
|
||||
last = property(lambda x: x._after is _last_iteration)
|
||||
index = property(lambda x: x.index0 + 1)
|
||||
@@ -368,6 +383,18 @@ class LoopContextBase(object):
|
||||
revindex0 = property(lambda x: x.length - x.index)
|
||||
depth = property(lambda x: x.depth0 + 1)
|
||||
|
||||
@property
|
||||
def previtem(self):
|
||||
if self._before is _first_iteration:
|
||||
return self._undefined('there is no previous item')
|
||||
return self._before
|
||||
|
||||
@property
|
||||
def nextitem(self):
|
||||
if self._after is _last_iteration:
|
||||
return self._undefined('there is no next item')
|
||||
return self._after
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
@@ -393,8 +420,8 @@ class LoopContextBase(object):
|
||||
|
||||
class LoopContext(LoopContextBase):
|
||||
|
||||
def __init__(self, iterable, recurse=None, depth0=0):
|
||||
LoopContextBase.__init__(self, recurse, depth0)
|
||||
def __init__(self, iterable, undefined, recurse=None, depth0=0):
|
||||
LoopContextBase.__init__(self, undefined, recurse, depth0)
|
||||
self._iterator = iter(iterable)
|
||||
|
||||
# try to get the length of the iterable early. This must be done
|
||||
@@ -446,9 +473,10 @@ class LoopContextIterator(object):
|
||||
ctx.index0 += 1
|
||||
if ctx._after is _last_iteration:
|
||||
raise StopIteration()
|
||||
next_elem = ctx._after
|
||||
ctx._before = ctx._current
|
||||
ctx._current = ctx._after
|
||||
ctx._after = ctx._safe_next()
|
||||
return next_elem, ctx
|
||||
return ctx._current, ctx
|
||||
|
||||
|
||||
class Macro(object):
|
||||
|
||||
Vendored
+1
-1
@@ -107,7 +107,7 @@ class _MagicFormatMapping(Mapping):
|
||||
"""This class implements a dummy wrapper to fix a bug in the Python
|
||||
standard library for string formatting.
|
||||
|
||||
See http://bugs.python.org/issue13598 for information about why
|
||||
See https://bugs.python.org/issue13598 for information about why
|
||||
this is necessary.
|
||||
"""
|
||||
|
||||
|
||||
Vendored
+22
-32
@@ -8,6 +8,7 @@
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import operator
|
||||
import re
|
||||
from collections import Mapping
|
||||
from jinja2.runtime import Undefined
|
||||
@@ -103,28 +104,6 @@ def test_sequence(value):
|
||||
return True
|
||||
|
||||
|
||||
def test_equalto(value, other):
|
||||
"""Check if an object has the same value as another object:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if foo.expression is equalto 42 %}
|
||||
the foo attribute evaluates to the constant 42
|
||||
{% endif %}
|
||||
|
||||
This appears to be a useless test as it does exactly the same as the
|
||||
``==`` operator, but it can be useful when used together with the
|
||||
`selectattr` function:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ users|selectattr("email", "equalto", "foo@bar.invalid") }}
|
||||
|
||||
.. versionadded:: 2.8
|
||||
"""
|
||||
return value == other
|
||||
|
||||
|
||||
def test_sameas(value, other):
|
||||
"""Check if an object points to the same memory address than another
|
||||
object:
|
||||
@@ -152,14 +131,12 @@ def test_escaped(value):
|
||||
return hasattr(value, '__html__')
|
||||
|
||||
|
||||
def test_greaterthan(value, other):
|
||||
"""Check if value is greater than other."""
|
||||
return value > other
|
||||
def test_in(value, seq):
|
||||
"""Check if value is in seq.
|
||||
|
||||
|
||||
def test_lessthan(value, other):
|
||||
"""Check if value is less than other."""
|
||||
return value < other
|
||||
.. versionadded:: 2.10
|
||||
"""
|
||||
return value in seq
|
||||
|
||||
|
||||
TESTS = {
|
||||
@@ -178,8 +155,21 @@ TESTS = {
|
||||
'iterable': test_iterable,
|
||||
'callable': test_callable,
|
||||
'sameas': test_sameas,
|
||||
'equalto': test_equalto,
|
||||
'escaped': test_escaped,
|
||||
'greaterthan': test_greaterthan,
|
||||
'lessthan': test_lessthan
|
||||
'in': test_in,
|
||||
'==': operator.eq,
|
||||
'eq': operator.eq,
|
||||
'equalto': operator.eq,
|
||||
'!=': operator.ne,
|
||||
'ne': operator.ne,
|
||||
'>': operator.gt,
|
||||
'gt': operator.gt,
|
||||
'greaterthan': operator.gt,
|
||||
'ge': operator.ge,
|
||||
'>=': operator.ge,
|
||||
'<': operator.lt,
|
||||
'lt': operator.lt,
|
||||
'lessthan': operator.lt,
|
||||
'<=': operator.le,
|
||||
'le': operator.le,
|
||||
}
|
||||
|
||||
Vendored
+24
-1
@@ -567,7 +567,7 @@ def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
|
||||
.replace(u'>', u'\\u003e') \
|
||||
.replace(u'&', u'\\u0026') \
|
||||
.replace(u"'", u'\\u0027')
|
||||
return rv
|
||||
return Markup(rv)
|
||||
|
||||
|
||||
@implements_iterator
|
||||
@@ -612,6 +612,29 @@ class Joiner(object):
|
||||
return self.sep
|
||||
|
||||
|
||||
class Namespace(object):
|
||||
"""A namespace object that can hold arbitrary attributes. It may be
|
||||
initialized from a dictionary or with keyword argments."""
|
||||
|
||||
def __init__(*args, **kwargs):
|
||||
self, args = args[0], args[1:]
|
||||
self.__attrs = dict(*args, **kwargs)
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name == '_Namespace__attrs':
|
||||
return object.__getattribute__(self, name)
|
||||
try:
|
||||
return self.__attrs[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
self.__attrs[name] = value
|
||||
|
||||
def __repr__(self):
|
||||
return '<Namespace %r>' % self.__attrs
|
||||
|
||||
|
||||
# does this python version support async for in and async generators?
|
||||
try:
|
||||
exec('async def _():\n async for _ in ():\n yield _')
|
||||
|
||||
Vendored
+118
-34
@@ -2,8 +2,8 @@ r'''Parse strings using a specification based on the Python format() syntax.
|
||||
|
||||
``parse()`` is the opposite of ``format()``
|
||||
|
||||
The module is set up to only export ``parse()``, ``search()`` and
|
||||
``findall()`` when ``import *`` is used:
|
||||
The module is set up to only export ``parse()``, ``search()``, ``findall()``,
|
||||
and ``with_pattern()`` when ``import *`` is used:
|
||||
|
||||
>>> from parse import *
|
||||
|
||||
@@ -37,6 +37,12 @@ compile it once:
|
||||
("compile" is not exported for ``import *`` usage as it would override the
|
||||
built-in ``compile()`` function)
|
||||
|
||||
The default behaviour is to match strings case insensitively. You may match with
|
||||
case by specifying `case_sensitive=True`:
|
||||
|
||||
>>> parse('SPAM', 'spam', case_sensitive=True) is None
|
||||
True
|
||||
|
||||
|
||||
Format Syntax
|
||||
-------------
|
||||
@@ -91,6 +97,9 @@ spam
|
||||
>>> print(r['quest']['name'])
|
||||
to seek the holy grail!
|
||||
|
||||
If the text you're matching has braces in it you can match those by including
|
||||
a double-brace ``{{`` or ``}}`` in your format string, just like format() does.
|
||||
|
||||
|
||||
Format Specification
|
||||
--------------------
|
||||
@@ -132,6 +141,7 @@ Type Characters Matched Output
|
||||
n Numbers with thousands separators (, or .) int
|
||||
% Percentage (converted to value/100.0) float
|
||||
f Fixed-point numbers float
|
||||
F Decimal numbers Decimal
|
||||
e Floating-point numbers with exponent float
|
||||
e.g. 1.1e-10, NAN (all case insensitive)
|
||||
g General number format (either d, f or e) float
|
||||
@@ -287,10 +297,47 @@ A more complete example of a custom type might be:
|
||||
... return yesno_mapping[text.lower()]
|
||||
|
||||
|
||||
If the type converter ``pattern`` uses regex-grouping (with parenthesis),
|
||||
you should indicate this by using the optional ``regex_group_count`` parameter
|
||||
in the ``with_pattern()`` decorator:
|
||||
|
||||
>>> @with_pattern(r'((\d+))', regex_group_count=2)
|
||||
... def parse_number2(text):
|
||||
... return int(text)
|
||||
>>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
|
||||
<Result (42, 43) {}>
|
||||
|
||||
Otherwise, this may cause parsing problems with unnamed/fixed parameters.
|
||||
|
||||
|
||||
Potential Gotchas
|
||||
-----------------
|
||||
|
||||
`parse()` will always match the shortest text necessary (from left to right)
|
||||
to fulfil the parse pattern, so for example:
|
||||
|
||||
>>> pattern = '{dir1}/{dir2}'
|
||||
>>> data = 'root/parent/subdir'
|
||||
>>> sorted(parse(pattern, data).named.items())
|
||||
[('dir1', 'root'), ('dir2', 'parent/subdir')]
|
||||
|
||||
So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit
|
||||
the pattern, the actual match represents the shortest successful match for
|
||||
`dir1`.
|
||||
|
||||
----
|
||||
|
||||
**Version history (in brief)**:
|
||||
|
||||
- 1.8.4 Add LICENSE file at request of packagers.
|
||||
Correct handling of AM/PM to follow most common interpretation.
|
||||
Correct parsing of hexadecimal that looks like a binary prefix.
|
||||
Add ability to parse case sensitively.
|
||||
Add parsing of numbers to Decimal with "F" (thanks John Vandenberg)
|
||||
- 1.8.3 Add regex_group_count to with_pattern() decorator to support
|
||||
user-defined types that contain brackets/parenthesis (thanks Jens Engel)
|
||||
- 1.8.2 add documentation for including braces in format string
|
||||
- 1.8.1 ensure bare hexadecimal digits are not matched
|
||||
- 1.8.0 support manual control over result evaluation (thanks Timo Furrer)
|
||||
- 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow
|
||||
more than 100 re groups in Python 3.5+ (thanks David King)
|
||||
@@ -340,12 +387,15 @@ A more complete example of a custom type might be:
|
||||
This code is copyright 2012-2017 Richard Jones <richard@python.org>
|
||||
See the end of the source file for the license of use.
|
||||
'''
|
||||
__version__ = '1.8.0'
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = '1.8.4'
|
||||
|
||||
# yes, I now have two problems
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, time, tzinfo, timedelta
|
||||
from decimal import Decimal
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
@@ -354,7 +404,7 @@ __all__ = 'parse search findall with_pattern'.split()
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def with_pattern(pattern):
|
||||
def with_pattern(pattern, regex_group_count=None):
|
||||
"""Attach a regular expression pattern matcher to a custom type converter
|
||||
function.
|
||||
|
||||
@@ -373,10 +423,12 @@ def with_pattern(pattern):
|
||||
>>> parse_number.pattern = r"\d+"
|
||||
|
||||
:param pattern: regular expression pattern (as text)
|
||||
:param regex_group_count: Indicates how many regex-groups are in pattern.
|
||||
:return: wrapped function
|
||||
"""
|
||||
def decorator(func):
|
||||
func.pattern = pattern
|
||||
func.regex_group_count = regex_group_count
|
||||
return func
|
||||
return decorator
|
||||
|
||||
@@ -388,6 +440,9 @@ def int_convert(base):
|
||||
|
||||
It may be of a base other than 10.
|
||||
|
||||
If may start with a base indicator, 0#nnnn, which we assume should
|
||||
override the specified base.
|
||||
|
||||
It may also have other non-numeric characters that we can ignore.
|
||||
'''
|
||||
CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
|
||||
@@ -398,7 +453,7 @@ def int_convert(base):
|
||||
else:
|
||||
sign = 1
|
||||
|
||||
if string[0] == '0' and len(string) > 1:
|
||||
if string[0] == '0' and len(string) > 2:
|
||||
if string[1] in 'bB':
|
||||
base = 2
|
||||
elif string[1] in 'oO':
|
||||
@@ -506,14 +561,18 @@ def date_convert(string, match, ymd=None, mdy=None, dmy=None,
|
||||
H = int(H)
|
||||
M = int(M)
|
||||
|
||||
day_incr = False
|
||||
if am is not None:
|
||||
am = groups[am]
|
||||
if am and am.strip() == 'PM':
|
||||
if am:
|
||||
am = am.strip()
|
||||
if am == 'AM' and H == 12:
|
||||
# correction for "12" hour functioning as "0" hour: 12:15 AM = 00:15 by 24 hr clock
|
||||
H -= 12
|
||||
elif am == 'PM' and H == 12:
|
||||
# no correction needed: 12PM is midday, 12:00 by 24 hour clock
|
||||
pass
|
||||
elif am == 'PM':
|
||||
H += 12
|
||||
if H > 23:
|
||||
day_incr = True
|
||||
H -= 24
|
||||
|
||||
if tz is not None:
|
||||
tz = groups[tz]
|
||||
@@ -548,9 +607,6 @@ def date_convert(string, match, ymd=None, mdy=None, dmy=None,
|
||||
d = int(d)
|
||||
d = datetime(y, m, d, H, M, S, u, tzinfo=tz)
|
||||
|
||||
if day_incr:
|
||||
d = d + timedelta(days=1)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
@@ -567,7 +623,7 @@ class RepeatedNameError(ValueError):
|
||||
REGEX_SAFETY = re.compile('([?\\\\.[\]()*+\^$!\|])')
|
||||
|
||||
# allowed field types
|
||||
ALLOWED_TYPES = set(list('nbox%fegwWdDsS') +
|
||||
ALLOWED_TYPES = set(list('nbox%fFegwWdDsS') +
|
||||
['t' + c for c in 'ieahgcts'])
|
||||
|
||||
|
||||
@@ -609,7 +665,7 @@ def extract_format(format, extra_types):
|
||||
# the rest is the type, if present
|
||||
type = format
|
||||
if type and type not in ALLOWED_TYPES and type not in extra_types:
|
||||
raise ValueError('type %r not recognised' % type)
|
||||
raise ValueError('format spec %r not recognised' % type)
|
||||
|
||||
return locals()
|
||||
|
||||
@@ -620,7 +676,7 @@ PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})"
|
||||
class Parser(object):
|
||||
'''Encapsulate a format string that may be used to parse other strings.
|
||||
'''
|
||||
def __init__(self, format, extra_types={}):
|
||||
def __init__(self, format, extra_types=None, case_sensitive=False):
|
||||
# a mapping of a name as in {hello.world} to a regex-group compatible
|
||||
# name, like hello__world Its used to prevent the transformation of
|
||||
# name-to-group and group to name to fail subtly, such as in:
|
||||
@@ -634,7 +690,13 @@ class Parser(object):
|
||||
self._name_types = {}
|
||||
|
||||
self._format = format
|
||||
if extra_types is None:
|
||||
extra_types = {}
|
||||
self._extra_types = extra_types
|
||||
if case_sensitive:
|
||||
self._re_flags = re.DOTALL
|
||||
else:
|
||||
self._re_flags = re.IGNORECASE | re.DOTALL
|
||||
self._fixed_fields = []
|
||||
self._named_fields = []
|
||||
self._group_index = 0
|
||||
@@ -643,7 +705,7 @@ class Parser(object):
|
||||
self.__search_re = None
|
||||
self.__match_re = None
|
||||
|
||||
log.debug('format %r -> %r' % (format, self._expression))
|
||||
log.debug('format %r -> %r', format, self._expression)
|
||||
|
||||
def __repr__(self):
|
||||
if len(self._format) > 20:
|
||||
@@ -655,8 +717,7 @@ class Parser(object):
|
||||
def _search_re(self):
|
||||
if self.__search_re is None:
|
||||
try:
|
||||
self.__search_re = re.compile(self._expression,
|
||||
re.IGNORECASE | re.DOTALL)
|
||||
self.__search_re = re.compile(self._expression, self._re_flags)
|
||||
except AssertionError:
|
||||
# access error through sys to keep py3k and backward compat
|
||||
e = str(sys.exc_info()[1])
|
||||
@@ -670,8 +731,7 @@ class Parser(object):
|
||||
if self.__match_re is None:
|
||||
expression = '^%s$' % self._expression
|
||||
try:
|
||||
self.__match_re = re.compile(expression,
|
||||
re.IGNORECASE | re.DOTALL)
|
||||
self.__match_re = re.compile(expression, self._re_flags)
|
||||
except AssertionError:
|
||||
# access error through sys to keep py3k and backward compat
|
||||
e = str(sys.exc_info()[1])
|
||||
@@ -721,8 +781,8 @@ class Parser(object):
|
||||
else:
|
||||
return Match(self, m)
|
||||
|
||||
def findall(self, string, pos=0, endpos=None, extra_types={}, evaluate_result=True):
|
||||
'''Search "string" for the all occurrances of "format".
|
||||
def findall(self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True):
|
||||
'''Search "string" for all occurrences of "format".
|
||||
|
||||
Optionally start the search at "pos" character index and limit the
|
||||
search to a maximum index of endpos - equivalent to
|
||||
@@ -821,7 +881,7 @@ class Parser(object):
|
||||
elif '_' in field:
|
||||
group = field.replace('_', '_' * n)
|
||||
else:
|
||||
raise KeyError('duplicated group name %r' % (field, ))
|
||||
raise KeyError('duplicated group name %r' % (field,))
|
||||
|
||||
# save off the mapping
|
||||
self._group_to_name_map[group] = field
|
||||
@@ -875,6 +935,10 @@ class Parser(object):
|
||||
if type in self._extra_types:
|
||||
type_converter = self._extra_types[type]
|
||||
s = getattr(type_converter, 'pattern', r'.+?')
|
||||
regex_group_count = getattr(type_converter, 'regex_group_count', 0)
|
||||
if regex_group_count is None:
|
||||
regex_group_count = 0
|
||||
self._group_index += regex_group_count
|
||||
|
||||
def f(string, m):
|
||||
return type_converter(string)
|
||||
@@ -902,6 +966,9 @@ class Parser(object):
|
||||
elif type == 'f':
|
||||
s = r'\d+\.\d+'
|
||||
self._type_conversions[group] = lambda s, m: float(s)
|
||||
elif type == 'F':
|
||||
s = r'\d+\.\d+'
|
||||
self._type_conversions[group] = lambda s, m: Decimal(s)
|
||||
elif type == 'e':
|
||||
s = r'\d+\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF'
|
||||
self._type_conversions[group] = lambda s, m: float(s)
|
||||
@@ -910,7 +977,7 @@ class Parser(object):
|
||||
self._group_index += 2
|
||||
self._type_conversions[group] = lambda s, m: float(s)
|
||||
elif type == 'd':
|
||||
s = r'\d+|0[xX][0-9a-fA-F]+|[0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+'
|
||||
s = r'\d+|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+'
|
||||
self._type_conversions[group] = int_convert(10)
|
||||
elif type == 'ti':
|
||||
s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \
|
||||
@@ -963,7 +1030,7 @@ class Parser(object):
|
||||
am=n + 4, tz=n + 5)
|
||||
self._group_index += 5
|
||||
elif type == 'ts':
|
||||
s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % (MONTHS_PAT)
|
||||
s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT
|
||||
n = self._group_index
|
||||
self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3,
|
||||
hms=n + 5)
|
||||
@@ -1089,7 +1156,7 @@ class ResultIterator(object):
|
||||
next = __next__
|
||||
|
||||
|
||||
def parse(format, string, extra_types={}, evaluate_result=True):
|
||||
def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False):
|
||||
'''Using "format" attempt to pull values from "string".
|
||||
|
||||
The format must match the string contents exactly. If the value
|
||||
@@ -1106,17 +1173,22 @@ def parse(format, string, extra_types={}, evaluate_result=True):
|
||||
.evaluate_result() - This will return a Result instance like you would get
|
||||
with ``evaluate_result`` set to True
|
||||
|
||||
The default behaviour is to match strings case insensitively. You may match with
|
||||
case by specifying case_sensitive=True.
|
||||
|
||||
If the format is invalid a ValueError will be raised.
|
||||
|
||||
See the module documentation for the use of "extra_types".
|
||||
|
||||
In the case there is no match parse() will return None.
|
||||
'''
|
||||
return Parser(format, extra_types=extra_types).parse(string, evaluate_result=evaluate_result)
|
||||
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
|
||||
return p.parse(string, evaluate_result=evaluate_result)
|
||||
|
||||
|
||||
def search(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=True):
|
||||
'''Search "string" for the first occurance of "format".
|
||||
def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True,
|
||||
case_sensitive=False):
|
||||
'''Search "string" for the first occurrence of "format".
|
||||
|
||||
The format may occur anywhere within the string. If
|
||||
instead you wish for the format to exactly match the string
|
||||
@@ -1135,17 +1207,22 @@ def search(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=T
|
||||
.evaluate_result() - This will return a Result instance like you would get
|
||||
with ``evaluate_result`` set to True
|
||||
|
||||
The default behaviour is to match strings case insensitively. You may match with
|
||||
case by specifying case_sensitive=True.
|
||||
|
||||
If the format is invalid a ValueError will be raised.
|
||||
|
||||
See the module documentation for the use of "extra_types".
|
||||
|
||||
In the case there is no match parse() will return None.
|
||||
'''
|
||||
return Parser(format, extra_types=extra_types).search(string, pos, endpos, evaluate_result=evaluate_result)
|
||||
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
|
||||
return p.search(string, pos, endpos, evaluate_result=evaluate_result)
|
||||
|
||||
|
||||
def findall(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=True):
|
||||
'''Search "string" for the all occurrances of "format".
|
||||
def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True,
|
||||
case_sensitive=False):
|
||||
'''Search "string" for all occurrences of "format".
|
||||
|
||||
You will be returned an iterator that holds Result instances
|
||||
for each format match found.
|
||||
@@ -1163,19 +1240,26 @@ def findall(format, string, pos=0, endpos=None, extra_types={}, evaluate_result=
|
||||
.evaluate_result() - This will return a Result instance like you would get
|
||||
with ``evaluate_result`` set to True
|
||||
|
||||
The default behaviour is to match strings case insensitively. You may match with
|
||||
case by specifying case_sensitive=True.
|
||||
|
||||
If the format is invalid a ValueError will be raised.
|
||||
|
||||
See the module documentation for the use of "extra_types".
|
||||
'''
|
||||
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
|
||||
return Parser(format, extra_types=extra_types).findall(string, pos, endpos, evaluate_result=evaluate_result)
|
||||
|
||||
|
||||
def compile(format, extra_types={}):
|
||||
def compile(format, extra_types=None, case_sensitive=False):
|
||||
'''Create a Parser instance to parse "format".
|
||||
|
||||
The resultant Parser has a method .parse(string) which
|
||||
behaves in the same manner as parse(format, string).
|
||||
|
||||
The default behaviour is to match strings case insensitively. You may match with
|
||||
case by specifying case_sensitive=True.
|
||||
|
||||
Use this function if you intend to parse many strings
|
||||
with the same format.
|
||||
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2017 Matthias C. M. Troffaes
|
||||
Copyright (c) 2012-2014 Antoine Pitrou and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+209
-105
@@ -1,3 +1,7 @@
|
||||
# Copyright (c) 2014-2017 Matthias C. M. Troffaes
|
||||
# Copyright (c) 2012-2014 Antoine Pitrou and contributors
|
||||
# Distributed under the terms of the MIT License.
|
||||
|
||||
import ctypes
|
||||
import fnmatch
|
||||
import functools
|
||||
@@ -9,9 +13,9 @@ import re
|
||||
import six
|
||||
import sys
|
||||
from collections import Sequence
|
||||
from contextlib import contextmanager
|
||||
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST
|
||||
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES
|
||||
from operator import attrgetter
|
||||
|
||||
from stat import (
|
||||
S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)
|
||||
try:
|
||||
@@ -24,23 +28,22 @@ try:
|
||||
intern = intern
|
||||
except NameError:
|
||||
intern = sys.intern
|
||||
try:
|
||||
basestring = basestring
|
||||
except NameError:
|
||||
basestring = str
|
||||
|
||||
supports_symlinks = True
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
import nt
|
||||
except ImportError:
|
||||
nt = None
|
||||
else:
|
||||
if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
|
||||
from nt import _getfinalpathname
|
||||
else:
|
||||
supports_symlinks = False
|
||||
_getfinalpathname = None
|
||||
else:
|
||||
nt = None
|
||||
|
||||
try:
|
||||
from os import scandir as os_scandir
|
||||
except ImportError:
|
||||
from scandir import scandir as os_scandir
|
||||
|
||||
__all__ = [
|
||||
"PurePath", "PurePosixPath", "PureWindowsPath",
|
||||
@@ -59,12 +62,15 @@ def _py2_fsencode(parts):
|
||||
else part for part in parts]
|
||||
|
||||
|
||||
def _try_except_fileexistserror(try_func, except_func):
|
||||
def _try_except_fileexistserror(try_func, except_func, else_func=None):
|
||||
if sys.version_info >= (3, 3):
|
||||
try:
|
||||
try_func()
|
||||
except FileExistsError as exc:
|
||||
except_func(exc)
|
||||
else:
|
||||
if else_func is not None:
|
||||
else_func()
|
||||
else:
|
||||
try:
|
||||
try_func()
|
||||
@@ -73,6 +79,45 @@ def _try_except_fileexistserror(try_func, except_func):
|
||||
raise
|
||||
else:
|
||||
except_func(exc)
|
||||
else:
|
||||
if else_func is not None:
|
||||
else_func()
|
||||
|
||||
|
||||
def _try_except_filenotfounderror(try_func, except_func):
|
||||
if sys.version_info >= (3, 3):
|
||||
try:
|
||||
try_func()
|
||||
except FileNotFoundError as exc:
|
||||
except_func(exc)
|
||||
else:
|
||||
try:
|
||||
try_func()
|
||||
except EnvironmentError as exc:
|
||||
if exc.errno != ENOENT:
|
||||
raise
|
||||
else:
|
||||
except_func(exc)
|
||||
|
||||
|
||||
def _try_except_permissionerror_iter(try_iter, except_iter):
|
||||
if sys.version_info >= (3, 3):
|
||||
try:
|
||||
for x in try_iter():
|
||||
yield x
|
||||
except PermissionError as exc:
|
||||
for x in except_iter(exc):
|
||||
yield x
|
||||
else:
|
||||
try:
|
||||
for x in try_iter():
|
||||
yield x
|
||||
except EnvironmentError as exc:
|
||||
if exc.errno not in (EPERM, EACCES):
|
||||
raise
|
||||
else:
|
||||
for x in except_iter(exc):
|
||||
yield x
|
||||
|
||||
|
||||
def _win32_get_unique_path_id(path):
|
||||
@@ -220,10 +265,7 @@ class _WindowsFlavour(_Flavour):
|
||||
|
||||
is_supported = (os.name == 'nt')
|
||||
|
||||
drive_letters = (
|
||||
set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
|
||||
set(chr(x) for x in range(ord('A'), ord('Z') + 1))
|
||||
)
|
||||
drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
ext_namespace_prefix = '\\\\?\\'
|
||||
|
||||
reserved_names = (
|
||||
@@ -283,12 +325,28 @@ class _WindowsFlavour(_Flavour):
|
||||
def casefold_parts(self, parts):
|
||||
return [p.lower() for p in parts]
|
||||
|
||||
def resolve(self, path):
|
||||
def resolve(self, path, strict=False):
|
||||
s = str(path)
|
||||
if not s:
|
||||
return os.getcwd()
|
||||
previous_s = None
|
||||
if _getfinalpathname is not None:
|
||||
return self._ext_to_normal(_getfinalpathname(s))
|
||||
if strict:
|
||||
return self._ext_to_normal(_getfinalpathname(s))
|
||||
else:
|
||||
# End of the path after the first one not found
|
||||
tail_parts = []
|
||||
while True:
|
||||
try:
|
||||
s = self._ext_to_normal(_getfinalpathname(s))
|
||||
except FileNotFoundError:
|
||||
previous_s = s
|
||||
s, tail = os.path.split(s)
|
||||
tail_parts.append(tail)
|
||||
if previous_s == s:
|
||||
return path
|
||||
else:
|
||||
return os.path.join(s, *reversed(tail_parts))
|
||||
# Means fallback on absolute
|
||||
return None
|
||||
|
||||
@@ -393,7 +451,7 @@ class _PosixFlavour(_Flavour):
|
||||
def casefold_parts(self, parts):
|
||||
return parts
|
||||
|
||||
def resolve(self, path):
|
||||
def resolve(self, path, strict=False):
|
||||
sep = self.sep
|
||||
accessor = path._accessor
|
||||
seen = {}
|
||||
@@ -424,9 +482,10 @@ class _PosixFlavour(_Flavour):
|
||||
try:
|
||||
target = accessor.readlink(newpath)
|
||||
except OSError as e:
|
||||
if e.errno != EINVAL:
|
||||
if e.errno != EINVAL and strict:
|
||||
raise
|
||||
# Not a symlink
|
||||
# Not a symlink, or non-strict mode. We just leave the path
|
||||
# untouched.
|
||||
path = newpath
|
||||
else:
|
||||
seen[newpath] = None # not resolved symlink
|
||||
@@ -463,6 +522,7 @@ class _PosixFlavour(_Flavour):
|
||||
raise RuntimeError("Can't determine home directory "
|
||||
"for %r" % username)
|
||||
|
||||
|
||||
_windows_flavour = _WindowsFlavour()
|
||||
_posix_flavour = _PosixFlavour()
|
||||
|
||||
@@ -495,6 +555,8 @@ class _NormalAccessor(_Accessor):
|
||||
|
||||
listdir = _wrap_strfunc(os.listdir)
|
||||
|
||||
scandir = _wrap_strfunc(os_scandir)
|
||||
|
||||
chmod = _wrap_strfunc(os.chmod)
|
||||
|
||||
if hasattr(os, "lchmod"):
|
||||
@@ -541,27 +603,6 @@ _normal_accessor = _NormalAccessor()
|
||||
# Globbing helpers
|
||||
#
|
||||
|
||||
@contextmanager
|
||||
def _cached(func):
|
||||
try:
|
||||
func.__cached__
|
||||
yield func
|
||||
except AttributeError:
|
||||
cache = {}
|
||||
|
||||
def wrapper(*args):
|
||||
try:
|
||||
return cache[args]
|
||||
except KeyError:
|
||||
value = cache[args] = func(*args)
|
||||
return value
|
||||
wrapper.__cached__ = True
|
||||
try:
|
||||
yield wrapper
|
||||
finally:
|
||||
cache.clear()
|
||||
|
||||
|
||||
def _make_selector(pattern_parts):
|
||||
pat = pattern_parts[0]
|
||||
child_parts = pattern_parts[1:]
|
||||
@@ -576,6 +617,7 @@ def _make_selector(pattern_parts):
|
||||
cls = _PreciseSelector
|
||||
return cls(pat, child_parts)
|
||||
|
||||
|
||||
if hasattr(functools, "lru_cache"):
|
||||
_make_selector = functools.lru_cache()(_make_selector)
|
||||
|
||||
@@ -589,8 +631,10 @@ class _Selector:
|
||||
self.child_parts = child_parts
|
||||
if child_parts:
|
||||
self.successor = _make_selector(child_parts)
|
||||
self.dironly = True
|
||||
else:
|
||||
self.successor = _TerminatingSelector()
|
||||
self.dironly = False
|
||||
|
||||
def select_from(self, parent_path):
|
||||
"""Iterate over all child paths of `parent_path` matched by this
|
||||
@@ -598,13 +642,15 @@ class _Selector:
|
||||
path_cls = type(parent_path)
|
||||
is_dir = path_cls.is_dir
|
||||
exists = path_cls.exists
|
||||
listdir = parent_path._accessor.listdir
|
||||
return self._select_from(parent_path, is_dir, exists, listdir)
|
||||
scandir = parent_path._accessor.scandir
|
||||
if not is_dir(parent_path):
|
||||
return iter([])
|
||||
return self._select_from(parent_path, is_dir, exists, scandir)
|
||||
|
||||
|
||||
class _TerminatingSelector:
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, listdir):
|
||||
def _select_from(self, parent_path, is_dir, exists, scandir):
|
||||
yield parent_path
|
||||
|
||||
|
||||
@@ -614,14 +660,20 @@ class _PreciseSelector(_Selector):
|
||||
self.name = name
|
||||
_Selector.__init__(self, child_parts)
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, listdir):
|
||||
if not is_dir(parent_path):
|
||||
def _select_from(self, parent_path, is_dir, exists, scandir):
|
||||
def try_iter():
|
||||
path = parent_path._make_child_relpath(self.name)
|
||||
if (is_dir if self.dironly else exists)(path):
|
||||
for p in self.successor._select_from(
|
||||
path, is_dir, exists, scandir):
|
||||
yield p
|
||||
|
||||
def except_iter(exc):
|
||||
return
|
||||
path = parent_path._make_child_relpath(self.name)
|
||||
if exists(path):
|
||||
for p in self.successor._select_from(
|
||||
path, is_dir, exists, listdir):
|
||||
yield p
|
||||
yield
|
||||
|
||||
for x in _try_except_permissionerror_iter(try_iter, except_iter):
|
||||
yield x
|
||||
|
||||
|
||||
class _WildcardSelector(_Selector):
|
||||
@@ -630,17 +682,26 @@ class _WildcardSelector(_Selector):
|
||||
self.pat = re.compile(fnmatch.translate(pat))
|
||||
_Selector.__init__(self, child_parts)
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, listdir):
|
||||
if not is_dir(parent_path):
|
||||
def _select_from(self, parent_path, is_dir, exists, scandir):
|
||||
def try_iter():
|
||||
cf = parent_path._flavour.casefold
|
||||
entries = list(scandir(parent_path))
|
||||
for entry in entries:
|
||||
if not self.dironly or entry.is_dir():
|
||||
name = entry.name
|
||||
casefolded = cf(name)
|
||||
if self.pat.match(casefolded):
|
||||
path = parent_path._make_child_relpath(name)
|
||||
for p in self.successor._select_from(
|
||||
path, is_dir, exists, scandir):
|
||||
yield p
|
||||
|
||||
def except_iter(exc):
|
||||
return
|
||||
cf = parent_path._flavour.casefold
|
||||
for name in listdir(parent_path):
|
||||
casefolded = cf(name)
|
||||
if self.pat.match(casefolded):
|
||||
path = parent_path._make_child_relpath(name)
|
||||
for p in self.successor._select_from(
|
||||
path, is_dir, exists, listdir):
|
||||
yield p
|
||||
yield
|
||||
|
||||
for x in _try_except_permissionerror_iter(try_iter, except_iter):
|
||||
yield x
|
||||
|
||||
|
||||
class _RecursiveWildcardSelector(_Selector):
|
||||
@@ -648,31 +709,46 @@ class _RecursiveWildcardSelector(_Selector):
|
||||
def __init__(self, pat, child_parts):
|
||||
_Selector.__init__(self, child_parts)
|
||||
|
||||
def _iterate_directories(self, parent_path, is_dir, listdir):
|
||||
def _iterate_directories(self, parent_path, is_dir, scandir):
|
||||
yield parent_path
|
||||
for name in listdir(parent_path):
|
||||
path = parent_path._make_child_relpath(name)
|
||||
if is_dir(path):
|
||||
for p in self._iterate_directories(path, is_dir, listdir):
|
||||
yield p
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, listdir):
|
||||
if not is_dir(parent_path):
|
||||
def try_iter():
|
||||
entries = list(scandir(parent_path))
|
||||
for entry in entries:
|
||||
if entry.is_dir() and not entry.is_symlink():
|
||||
path = parent_path._make_child_relpath(entry.name)
|
||||
for p in self._iterate_directories(path, is_dir, scandir):
|
||||
yield p
|
||||
|
||||
def except_iter(exc):
|
||||
return
|
||||
with _cached(listdir) as listdir:
|
||||
yield
|
||||
|
||||
for x in _try_except_permissionerror_iter(try_iter, except_iter):
|
||||
yield x
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, scandir):
|
||||
def try_iter():
|
||||
yielded = set()
|
||||
try:
|
||||
successor_select = self.successor._select_from
|
||||
for starting_point in self._iterate_directories(
|
||||
parent_path, is_dir, listdir):
|
||||
parent_path, is_dir, scandir):
|
||||
for p in successor_select(
|
||||
starting_point, is_dir, exists, listdir):
|
||||
starting_point, is_dir, exists, scandir):
|
||||
if p not in yielded:
|
||||
yield p
|
||||
yielded.add(p)
|
||||
finally:
|
||||
yielded.clear()
|
||||
|
||||
def except_iter(exc):
|
||||
return
|
||||
yield
|
||||
|
||||
for x in _try_except_permissionerror_iter(try_iter, except_iter):
|
||||
yield x
|
||||
|
||||
|
||||
#
|
||||
# Public API
|
||||
@@ -743,13 +819,25 @@ class PurePath(object):
|
||||
for a in args:
|
||||
if isinstance(a, PurePath):
|
||||
parts += a._parts
|
||||
elif isinstance(a, basestring):
|
||||
# Force-cast str subclasses to str (issue #21127)
|
||||
parts.append(str(a))
|
||||
else:
|
||||
raise TypeError(
|
||||
"argument should be a path or str object, not %r"
|
||||
% type(a))
|
||||
if sys.version_info >= (3, 6):
|
||||
a = os.fspath(a)
|
||||
else:
|
||||
# duck typing for older Python versions
|
||||
if hasattr(a, "__fspath__"):
|
||||
a = a.__fspath__()
|
||||
if isinstance(a, str):
|
||||
# Force-cast str subclasses to str (issue #21127)
|
||||
parts.append(str(a))
|
||||
# also handle unicode for PY2 (six.text_type = unicode)
|
||||
elif six.PY2 and isinstance(a, six.text_type):
|
||||
# cast to str using filesystem encoding
|
||||
parts.append(a.encode(sys.getfilesystemencoding()))
|
||||
else:
|
||||
raise TypeError(
|
||||
"argument should be a str object or an os.PathLike "
|
||||
"object returning str, not %r"
|
||||
% type(a))
|
||||
return cls._flavour.parse_parts(parts)
|
||||
|
||||
@classmethod
|
||||
@@ -783,7 +871,7 @@ class PurePath(object):
|
||||
return cls._flavour.join(parts)
|
||||
|
||||
def _init(self):
|
||||
# Overriden in concrete Path
|
||||
# Overridden in concrete Path
|
||||
pass
|
||||
|
||||
def _make_child(self, args):
|
||||
@@ -802,6 +890,9 @@ class PurePath(object):
|
||||
self._parts) or '.'
|
||||
return self._str
|
||||
|
||||
def __fspath__(self):
|
||||
return str(self)
|
||||
|
||||
def as_posix(self):
|
||||
"""Return the string representation of the path with forward (/)
|
||||
slashes."""
|
||||
@@ -982,7 +1073,7 @@ class PurePath(object):
|
||||
cf = self._flavour.casefold_parts
|
||||
if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
|
||||
formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
|
||||
raise ValueError("{!r} does not start with {!r}"
|
||||
raise ValueError("{0!r} does not start with {1!r}"
|
||||
.format(str(self), str(formatted)))
|
||||
return self._from_parsed_parts('', root if n == 1 else '',
|
||||
abs_parts[n:])
|
||||
@@ -1070,6 +1161,12 @@ class PurePath(object):
|
||||
return True
|
||||
|
||||
|
||||
# Can't subclass os.PathLike from PurePath and keep the constructor
|
||||
# optimizations in PurePath._parse_args().
|
||||
if sys.version_info >= (3, 6):
|
||||
os.PathLike.register(PurePath)
|
||||
|
||||
|
||||
class PurePosixPath(PurePath):
|
||||
_flavour = _posix_flavour
|
||||
__slots__ = ()
|
||||
@@ -1156,8 +1253,8 @@ class Path(PurePath):
|
||||
return cls(cls()._flavour.gethomedir(None))
|
||||
|
||||
def samefile(self, other_path):
|
||||
"""Return whether `other_file` is the same or not as this file.
|
||||
(as returned by os.path.samefile(file, other_file)).
|
||||
"""Return whether other_path is the same or not as this file
|
||||
(as returned by os.path.samefile()).
|
||||
"""
|
||||
if hasattr(os.path, "samestat"):
|
||||
st = self.stat()
|
||||
@@ -1191,6 +1288,8 @@ class Path(PurePath):
|
||||
"""Iterate over this subtree and yield all existing files (of any
|
||||
kind, including directories) matching the given pattern.
|
||||
"""
|
||||
if not pattern:
|
||||
raise ValueError("Unacceptable pattern: {0!r}".format(pattern))
|
||||
pattern = self._flavour.casefold(pattern)
|
||||
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
||||
if drv or root:
|
||||
@@ -1229,7 +1328,7 @@ class Path(PurePath):
|
||||
obj._init(template=self)
|
||||
return obj
|
||||
|
||||
def resolve(self):
|
||||
def resolve(self, strict=False):
|
||||
"""
|
||||
Make the path absolute, resolving all symlinks on the way and also
|
||||
normalizing it (for example turning slashes into backslashes under
|
||||
@@ -1237,7 +1336,7 @@ class Path(PurePath):
|
||||
"""
|
||||
if self._closed:
|
||||
self._raise_closed()
|
||||
s = self._flavour.resolve(self)
|
||||
s = self._flavour.resolve(self, strict=strict)
|
||||
if s is None:
|
||||
# No symlink resolution => for consistency, raise an error if
|
||||
# the path doesn't exist or is forbidden
|
||||
@@ -1307,7 +1406,7 @@ class Path(PurePath):
|
||||
if not isinstance(data, six.binary_type):
|
||||
raise TypeError(
|
||||
'data must be %s, not %s' %
|
||||
(six.binary_type.__class__.__name__, data.__class__.__name__))
|
||||
(six.binary_type.__name__, data.__class__.__name__))
|
||||
with self.open(mode='wb') as f:
|
||||
return f.write(data)
|
||||
|
||||
@@ -1318,7 +1417,7 @@ class Path(PurePath):
|
||||
if not isinstance(data, six.text_type):
|
||||
raise TypeError(
|
||||
'data must be %s, not %s' %
|
||||
(six.text_type.__class__.__name__, data.__class__.__name__))
|
||||
(six.text_type.__name__, data.__class__.__name__))
|
||||
with self.open(mode='w', encoding=encoding, errors=errors) as f:
|
||||
return f.write(data)
|
||||
|
||||
@@ -1346,27 +1445,26 @@ class Path(PurePath):
|
||||
os.close(fd)
|
||||
|
||||
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
||||
|
||||
def helper(exc):
|
||||
if not exist_ok or not self.is_dir():
|
||||
raise exc
|
||||
|
||||
"""
|
||||
Create a new directory at this given path.
|
||||
"""
|
||||
if self._closed:
|
||||
self._raise_closed()
|
||||
if not parents:
|
||||
_try_except_fileexistserror(
|
||||
lambda: self._accessor.mkdir(self, mode),
|
||||
helper)
|
||||
else:
|
||||
try:
|
||||
_try_except_fileexistserror(
|
||||
lambda: self._accessor.mkdir(self, mode),
|
||||
helper)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
self.parent.mkdir(parents=True)
|
||||
self._accessor.mkdir(self, mode)
|
||||
|
||||
def _try_func():
|
||||
self._accessor.mkdir(self, mode)
|
||||
|
||||
def _exc_func(exc):
|
||||
if not parents or self.parent == self:
|
||||
raise exc
|
||||
self.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.mkdir(mode, parents=False, exist_ok=exist_ok)
|
||||
|
||||
try:
|
||||
_try_except_filenotfounderror(_try_func, _exc_func)
|
||||
except OSError:
|
||||
if not exist_ok or not self.is_dir():
|
||||
raise
|
||||
|
||||
def chmod(self, mode):
|
||||
"""
|
||||
@@ -1564,3 +1662,9 @@ class PosixPath(Path, PurePosixPath):
|
||||
|
||||
class WindowsPath(Path, PureWindowsPath):
|
||||
__slots__ = ()
|
||||
|
||||
def owner(self):
|
||||
raise NotImplementedError("Path.owner() is unsupported on this system")
|
||||
|
||||
def group(self):
|
||||
raise NotImplementedError("Path.group() is unsupported on this system")
|
||||
Vendored
+1
-1
@@ -75,7 +75,7 @@ if sys.platform != 'win32':
|
||||
from .pty_spawn import spawn, spawnu
|
||||
from .run import run, runu
|
||||
|
||||
__version__ = '4.5.0'
|
||||
__version__ = '4.6.0'
|
||||
__revision__ = ''
|
||||
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
|
||||
'which', 'split_command_line', '__version__', '__revision__']
|
||||
|
||||
Vendored
+12
-5
@@ -18,11 +18,6 @@ from .exceptions import EOF
|
||||
from .utils import string_types
|
||||
|
||||
class PopenSpawn(SpawnBase):
|
||||
if PY3:
|
||||
crlf = '\n'.encode('ascii')
|
||||
else:
|
||||
crlf = '\n'
|
||||
|
||||
def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, encoding=None,
|
||||
codec_errors='strict', preexec_fn=None):
|
||||
@@ -30,6 +25,18 @@ class PopenSpawn(SpawnBase):
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
|
||||
# Note that `SpawnBase` initializes `self.crlf` to `\r\n`
|
||||
# because the default behaviour for a PTY is to convert
|
||||
# incoming LF to `\r\n` (see the `onlcr` flag and
|
||||
# https://stackoverflow.com/a/35887657/5397009). Here we set
|
||||
# it to `os.linesep` because that is what the spawned
|
||||
# application outputs by default and `popen` doesn't translate
|
||||
# anything.
|
||||
if encoding is None:
|
||||
self.crlf = os.linesep.encode ("ascii")
|
||||
else:
|
||||
self.crlf = self.string_type (os.linesep)
|
||||
|
||||
kwargs = dict(bufsize=0, stdin=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
|
||||
cwd=cwd, preexec_fn=preexec_fn, env=env)
|
||||
|
||||
Vendored
+15
-7
@@ -259,7 +259,7 @@ class pxssh (spawn):
|
||||
sync_multiplier=1, check_local_ip=True,
|
||||
password_regex=r'(?i)(?:password:)|(?:passphrase for key)',
|
||||
ssh_tunnels={}, spawn_local_ssh=True,
|
||||
sync_original_prompt=True):
|
||||
sync_original_prompt=True, ssh_config=None):
|
||||
'''This logs the user into the given server.
|
||||
|
||||
It uses
|
||||
@@ -294,8 +294,15 @@ class pxssh (spawn):
|
||||
session to do so. Setting this option to `False` and not having an active session
|
||||
will trigger an error.
|
||||
|
||||
Set ``ssh_key`` to `True` to force passing the current SSH authentication socket to the
|
||||
Set ``ssh_key`` to a file path to an SSH private key to use that SSH key
|
||||
for the session authentication.
|
||||
Set ``ssh_key`` to `True` to force passing the current SSH authentication socket
|
||||
to the desired ``hostname``.
|
||||
|
||||
Set ``ssh_config`` to a file path string of an SSH client config file to pass that
|
||||
file to the client to handle itself. You may set any options you wish in here, however
|
||||
doing so will require you to post extra information that you may not want to if you
|
||||
run into issues.
|
||||
'''
|
||||
|
||||
session_regex_array = ["(?i)are you sure you want to continue connecting", original_prompt, password_regex, "(?i)permission denied", "(?i)terminal type", TIMEOUT]
|
||||
@@ -310,6 +317,10 @@ class pxssh (spawn):
|
||||
ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'"
|
||||
if self.force_password:
|
||||
ssh_options = ssh_options + ' ' + self.SSH_OPTS
|
||||
if ssh_config is not None:
|
||||
if spawn_local_ssh and not os.path.isfile(ssh_config):
|
||||
raise ExceptionPxssh('SSH config does not exist or is not a file.')
|
||||
ssh_options = ssh_options + '-F ' + ssh_config
|
||||
if port is not None:
|
||||
ssh_options = ssh_options + ' -p %s'%(str(port))
|
||||
if ssh_key is not None:
|
||||
@@ -317,11 +328,8 @@ class pxssh (spawn):
|
||||
if ssh_key==True:
|
||||
ssh_options = ssh_options + ' -A'
|
||||
else:
|
||||
try:
|
||||
if spawn_local_ssh:
|
||||
os.path.isfile(ssh_key)
|
||||
except:
|
||||
raise ExceptionPxssh('private ssh key does not exist')
|
||||
if spawn_local_ssh and not os.path.isfile(ssh_key):
|
||||
raise ExceptionPxssh('private ssh key does not exist or is not a file.')
|
||||
ssh_options = ssh_options + ' -i %s' % (ssh_key)
|
||||
|
||||
# SSH tunnels, make sure you know what you're putting into the lists
|
||||
|
||||
Vendored
+1
-1
@@ -165,7 +165,7 @@ def poll_ignore_interrupts(fds, timeout=None):
|
||||
|
||||
poller = select.poll()
|
||||
for fd in fds:
|
||||
poller.register(fd)
|
||||
poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
|
||||
|
||||
while True:
|
||||
try:
|
||||
|
||||
+7
-1
@@ -1 +1,7 @@
|
||||
__version__ = '0.0.5'
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
__version__ = "0.1.2"
|
||||
|
||||
__all__ = ["Finder", "WindowsFinder", "SystemPath"]
|
||||
from .pythonfinder import Finder
|
||||
from .models import SystemPath, WindowsFinder
|
||||
|
||||
+1
-1
@@ -8,5 +8,5 @@ PYTHONFINDER_PACKAGE = os.path.dirname(PYTHONFINDER_MAIN)
|
||||
|
||||
from pythonfinder import cli as cli
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
sys.exit(cli())
|
||||
|
||||
Vendored
+41
-21
@@ -5,35 +5,55 @@ import click
|
||||
import crayons
|
||||
import sys
|
||||
from . import __version__
|
||||
from .pythonfinder import PythonFinder
|
||||
from .pythonfinder import Finder
|
||||
|
||||
|
||||
# @click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
||||
@click.command()
|
||||
@click.option('--find', default=False, nargs=1, help="Find a specific python version.")
|
||||
@click.option('--findall', is_flag=True, default=False, help="Find all python versions.")
|
||||
@click.option("--find", default=False, nargs=1, help="Find a specific python version.")
|
||||
@click.option("--which", default=False, nargs=1, help="Run the which commend.")
|
||||
@click.option(
|
||||
"--findall", is_flag=True, default=False, help="Find all python versions."
|
||||
)
|
||||
@click.option(
|
||||
"--version", is_flag=True, default=False, help="Display PythonFinder version."
|
||||
)
|
||||
# @click.version_option(prog_name=crayons.normal('pyfinder', bold=True), version=__version__)
|
||||
@click.pass_context
|
||||
def cli(
|
||||
ctx, find=False, findall=False
|
||||
):
|
||||
if not find and not findall:
|
||||
click.echo('Please provide a command', color='red')
|
||||
sys.exit(1)
|
||||
if find:
|
||||
if any([find.startswith('{0}'.format(n)) for n in range(10)]):
|
||||
found = PythonFinder.from_version(find.strip())
|
||||
else:
|
||||
found = PythonFinder.from_line()
|
||||
if found:
|
||||
click.echo('Found Python Version: {0}'.format(found), color='white')
|
||||
sys.exit(0)
|
||||
else:
|
||||
#TODO: implement this
|
||||
click.echo('This is not yet implemented')
|
||||
def cli(ctx, find=False, which=False, findall=False, version=False):
|
||||
if version:
|
||||
click.echo(
|
||||
"{0} version {1}".format(
|
||||
crayons.white("PythonFinder", bold=True), crayons.yellow(__version__)
|
||||
)
|
||||
)
|
||||
sys.exit(0)
|
||||
finder = Finder()
|
||||
if find:
|
||||
|
||||
if any([find.startswith("{0}".format(n)) for n in range(10)]):
|
||||
found = finder.find_python_version(find.strip())
|
||||
else:
|
||||
found = finder.system_path.python_executables
|
||||
if found:
|
||||
click.echo("Found Python Version: {0}".format(found), color="white")
|
||||
sys.exit(0)
|
||||
else:
|
||||
click.echo("Failed to find matching executable...")
|
||||
sys.exit(1)
|
||||
elif which:
|
||||
found = finder.system_path.which(which.strip())
|
||||
if found:
|
||||
click.echo("Found Executable: {0}".format(found), color="white")
|
||||
sys.exit(0)
|
||||
else:
|
||||
click.echo("Failed to find matching executable...")
|
||||
sys.exit(1)
|
||||
else:
|
||||
click.echo("Please provide a command", color="red")
|
||||
sys.exit(1)
|
||||
sys.exit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
PYENV_INSTALLED = bool(os.environ.get("PYENV_SHELL")) or bool(
|
||||
os.environ.get("PYENV_ROOT")
|
||||
)
|
||||
PYENV_ROOT = os.path.expandvars(os.environ.get("PYENV_ROOT", "~/.pyenv"))
|
||||
IS_64BIT_OS = None
|
||||
SYSTEM_ARCH = platform.architecture()[0]
|
||||
|
||||
if sys.maxsize > 2 ** 32:
|
||||
IS_64BIT_OS = platform.machine() == "AMD64"
|
||||
else:
|
||||
IS_64BIT_OS = False
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import abc
|
||||
import operator
|
||||
import six
|
||||
from ..utils import KNOWN_EXTS
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseFinder(object):
|
||||
def get_versions(self):
|
||||
"""Return the available versions from the finder"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def version_paths(self):
|
||||
return self.versions.values()
|
||||
|
||||
@property
|
||||
def expanded_paths(self):
|
||||
return (p.paths.values() for p in self.version_paths)
|
||||
|
||||
|
||||
class BasePath(object):
|
||||
def which(self, name):
|
||||
"""Search in this path for an executable.
|
||||
|
||||
:param executable: The name of an executable to search for.
|
||||
:type executable: str
|
||||
:returns: :class:`~pythonfinder.models.PathEntry` instance.
|
||||
"""
|
||||
|
||||
valid_names = [
|
||||
"{0}.{1}".format(name, ext).lower() if ext else "{0}".format(name).lower()
|
||||
for ext in KNOWN_EXTS
|
||||
]
|
||||
finder = filter(operator.attrgetter("is_executable"), self.children.values())
|
||||
name_getter = operator.attrgetter("path.name")
|
||||
return next(
|
||||
(child for child in finder if name_getter(child).lower() in valid_names),
|
||||
None,
|
||||
)
|
||||
|
||||
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
|
||||
"""Search or self for the specified Python version and return the first match.
|
||||
|
||||
:param major: Major version number.
|
||||
:type major: int
|
||||
:param minor: Minor python version, defaults to None
|
||||
:param minor: int, optional
|
||||
:param patch: Patch python version, defaults to None
|
||||
:param patch: int, optional
|
||||
:returns: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
|
||||
"""
|
||||
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches", major, minor=minor, patch=patch, pre=pre, dev=dev
|
||||
)
|
||||
is_py = operator.attrgetter("is_python")
|
||||
py_version = operator.attrgetter("as_python")
|
||||
if not self.is_dir:
|
||||
if self.is_python:
|
||||
return self if version_matcher(self.as_python) else None
|
||||
return
|
||||
finder = (c for c in self.children.values() if is_py(c) and py_version(c))
|
||||
py_filter = filter(
|
||||
None, filter(lambda c: version_matcher(py_version(c)), finder)
|
||||
)
|
||||
version_sort = operator.attrgetter("py_version.version")
|
||||
return next(
|
||||
(c for c in sorted(py_filter, key=version_sort, reverse=True)), None
|
||||
)
|
||||
|
||||
|
||||
from .path import SystemPath
|
||||
from .windows import WindowsFinder
|
||||
from .python import PythonVersion
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import attr
|
||||
import operator
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from . import BasePath
|
||||
from .python import PythonVersion
|
||||
from ..environment import PYENV_INSTALLED, PYENV_ROOT
|
||||
from ..utils import (
|
||||
optional_instance_of,
|
||||
filter_pythons,
|
||||
path_is_known_executable,
|
||||
is_python_name,
|
||||
ensure_path,
|
||||
)
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
|
||||
@attr.s
|
||||
class SystemPath(object):
|
||||
paths = attr.ib(default=attr.Factory(defaultdict))
|
||||
_executables = attr.ib(default=attr.Factory(list))
|
||||
_python_executables = attr.ib(default=attr.Factory(list))
|
||||
path_order = attr.ib(default=attr.Factory(list))
|
||||
python_version_dict = attr.ib()
|
||||
only_python = attr.ib(default=False)
|
||||
pyenv_finder = attr.ib(default=None, validator=optional_instance_of("PyenvPath"))
|
||||
system = attr.ib(default=False)
|
||||
|
||||
@property
|
||||
def executables(self):
|
||||
if not self._executables:
|
||||
self._executables = [p for p in self.paths.values() if p.is_executable]
|
||||
return self._executables
|
||||
|
||||
@property
|
||||
def python_executables(self):
|
||||
if not self._python_executables:
|
||||
self._python_executables = [p for p in self.paths.values() if p.is_python]
|
||||
return self._python_executables
|
||||
|
||||
@python_version_dict.default
|
||||
def get_python_version_dict(self):
|
||||
version_dict = defaultdict(list)
|
||||
for p in self.python_executables:
|
||||
try:
|
||||
version_object = PythonVersion.from_path(p)
|
||||
except ValueError:
|
||||
continue
|
||||
version_dict[version_object.version_tuple].append(version_object)
|
||||
return version_dict
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
#: slice in pyenv
|
||||
if not self.__class__ == SystemPath:
|
||||
return
|
||||
if os.name == "nt":
|
||||
self._setup_windows()
|
||||
if PYENV_INSTALLED:
|
||||
self._setup_pyenv()
|
||||
venv = os.environ.get('VIRTUAL_ENV')
|
||||
if venv:
|
||||
if os.name == 'nt':
|
||||
bin_dir = 'Scripts'
|
||||
else:
|
||||
bin_dir = 'bin'
|
||||
p = Path(venv)
|
||||
self.path_order = [(p / bin_dir).as_posix()] + self.path_order
|
||||
self.paths[p] = PathEntry.create(
|
||||
path=p, is_root=True, only_python=False
|
||||
)
|
||||
if self.system:
|
||||
syspath = Path(sys.executable)
|
||||
self.path_order = [syspath.parent.as_posix()] + self.path_order
|
||||
self.paths[syspath.parent.as_posix()] = PathEntry.create(
|
||||
path=syspath.parent, is_root=True, only_python=True
|
||||
)
|
||||
|
||||
def _setup_pyenv(self):
|
||||
from .pyenv import PyenvFinder
|
||||
|
||||
last_pyenv = next(
|
||||
(p for p in reversed(self.path_order) if PYENV_ROOT.lower() in p.lower()),
|
||||
None,
|
||||
)
|
||||
pyenv_index = self.path_order.index(last_pyenv)
|
||||
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT)
|
||||
# paths = (v.paths.values() for v in self.pyenv_finder.versions.values())
|
||||
root_paths = (
|
||||
p for path in self.pyenv_finder.expanded_paths for p in path if p.is_root
|
||||
)
|
||||
before_path = self.path_order[: pyenv_index + 1]
|
||||
after_path = self.path_order[pyenv_index + 2 :]
|
||||
self.path_order = (
|
||||
before_path + [p.path.as_posix() for p in root_paths] + after_path
|
||||
)
|
||||
self.paths.update({p.path: p for p in root_paths})
|
||||
|
||||
def _setup_windows(self):
|
||||
from .windows import WindowsFinder
|
||||
|
||||
self.windows_finder = WindowsFinder.create()
|
||||
root_paths = (p for p in self.windows_finder.paths if p.is_root)
|
||||
path_addition = [p.path.as_posix() for p in root_paths]
|
||||
self.path_order = self.path_order[:] + path_addition
|
||||
self.paths.update({p.path: p for p in root_paths})
|
||||
|
||||
def get_path(self, path):
|
||||
_path = self.paths.get(path)
|
||||
if not _path and path in self.path_order:
|
||||
self.paths[path] = PathEntry.create(
|
||||
path=path, is_root=True, only_python=self.only_python
|
||||
)
|
||||
return self.paths.get(path)
|
||||
|
||||
def which(self, executable):
|
||||
"""Search for an executable on the path.
|
||||
|
||||
:param executable: Name of the executable to be located.
|
||||
:type executable: str
|
||||
:returns: :class:`~pythonfinder.models.PathEntry` object.
|
||||
"""
|
||||
sub_which = operator.methodcaller("which", name=executable)
|
||||
return next(
|
||||
(sub_which(self.get_path(k)) for k in self.path_order), None
|
||||
)
|
||||
|
||||
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
|
||||
"""Search for a specific python version on the path.
|
||||
|
||||
:param major: Major python version to search for.
|
||||
:type major: int
|
||||
:param minor: Minor python version to search for, defaults to None
|
||||
:param minor: int, optional
|
||||
:param path: Patch python version to search for, defaults to None
|
||||
:param path: int, optional
|
||||
:return: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
|
||||
:rtype: :class:`~pythonfinder.models.PathEntry`
|
||||
"""
|
||||
|
||||
sub_finder = operator.methodcaller(
|
||||
"find_python_version", major, minor=minor, patch=patch, pre=pre, dev=dev
|
||||
)
|
||||
if os.name == "nt" and self.windows_finder:
|
||||
windows_finder_version = sub_finder(self.windows_finder)
|
||||
if windows_finder_version:
|
||||
return windows_finder_version
|
||||
paths = [self.get_path(k) for k in self.path_order]
|
||||
path_filter = filter(None, [sub_finder(p) for p in paths])
|
||||
version_sort = operator.attrgetter("as_python.version")
|
||||
return next(
|
||||
(c for c in sorted(path_filter, key=version_sort, reverse=True)), None
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, path=None, system=False, only_python=False):
|
||||
"""Create a new :class:`pythonfinder.models.SystemPath` instance.
|
||||
|
||||
:param path: Search path to prepend when searching, defaults to None
|
||||
:param path: str, optional
|
||||
:param system: Whether to use the running python by default instead of searching, defaults to False
|
||||
:param system: bool, optional
|
||||
:param only_python: Whether to search only for python executables, defaults to False
|
||||
:param only_python: bool, optional
|
||||
:return: A new :class:`pythonfinder.models.SystemPath` instance.
|
||||
:rtype: :class:`pythonfinder.models.SystemPath`
|
||||
"""
|
||||
|
||||
path_entries = defaultdict(PathEntry)
|
||||
paths = os.environ.get("PATH").split(os.pathsep)
|
||||
if path:
|
||||
paths = [path] + paths
|
||||
_path_objects = [ensure_path(p.strip('"')) for p in paths]
|
||||
paths = [p.as_posix() for p in _path_objects]
|
||||
path_entries.update(
|
||||
{
|
||||
p.as_posix(): PathEntry.create(
|
||||
path=p, is_root=True, only_python=only_python
|
||||
)
|
||||
for p in _path_objects
|
||||
}
|
||||
)
|
||||
return cls(paths=path_entries, path_order=paths, only_python=only_python, system=system)
|
||||
|
||||
|
||||
@attr.s
|
||||
class PathEntry(BasePath):
|
||||
path = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
_children = attr.ib(default=attr.Factory(dict))
|
||||
is_root = attr.ib(default=True)
|
||||
only_python = attr.ib(default=False)
|
||||
py_version = attr.ib(default=None)
|
||||
pythons = attr.ib(default=None)
|
||||
|
||||
def _filter_children(self):
|
||||
if self.only_python:
|
||||
children = filter_pythons(self.path)
|
||||
else:
|
||||
children = self.path.iterdir()
|
||||
return children
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
if not self._children and self.is_dir and self.is_root:
|
||||
self._children = {
|
||||
child.as_posix(): PathEntry(path=child, is_root=False)
|
||||
for child in self._filter_children()
|
||||
}
|
||||
return self._children
|
||||
|
||||
@property
|
||||
def as_python(self):
|
||||
if not self.is_dir and self.is_python:
|
||||
if not self.py_version:
|
||||
try:
|
||||
from .python import PythonVersion
|
||||
|
||||
self.py_version = PythonVersion.from_path(self.path)
|
||||
except ValueError:
|
||||
self.py_version = None
|
||||
return self.py_version
|
||||
|
||||
@classmethod
|
||||
def create(cls, path, is_root=False, only_python=False, pythons=None):
|
||||
"""Helper method for creating new :class:`pythonfinder.models.PathEntry` instances.
|
||||
|
||||
:param path: Path to the specified location.
|
||||
:type path: str
|
||||
:param is_root: Whether this is a root from the environment PATH variable, defaults to False
|
||||
:param is_root: bool, optional
|
||||
:param only_python: Whether to search only for python executables, defaults to False
|
||||
:param only_python: bool, optional
|
||||
:param pythons: A dictionary of existing python objects (usually from a finder), defaults to None
|
||||
:param pythons: dict, optional
|
||||
:return: A new instance of the class.
|
||||
:rtype: :class:`pythonfinder.models.PathEntry`
|
||||
"""
|
||||
|
||||
target = ensure_path(path)
|
||||
_new = cls(
|
||||
path=target, is_root=is_root, only_python=only_python, pythons=pythons
|
||||
)
|
||||
if pythons and only_python:
|
||||
children = {}
|
||||
for pth, python in pythons.items():
|
||||
pth = ensure_path(pth)
|
||||
children[pth.as_posix()] = PathEntry(
|
||||
path=pth, is_root=False, only_python=only_python, py_version=python
|
||||
)
|
||||
_new._children = children
|
||||
return _new
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.path.name
|
||||
|
||||
@property
|
||||
def is_dir(self):
|
||||
return self.path.is_dir()
|
||||
|
||||
@property
|
||||
def is_executable(self):
|
||||
return path_is_known_executable(self.path)
|
||||
|
||||
@property
|
||||
def is_python(self):
|
||||
return self.is_executable and (
|
||||
self.py_version or is_python_name(self.path.name)
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
class VersionPath(SystemPath):
|
||||
base = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
|
||||
@classmethod
|
||||
def create(cls, path, only_python=True, pythons=None):
|
||||
"""Accepts a path to a base python version directory.
|
||||
|
||||
Generates the pyenv version listings for it"""
|
||||
path = ensure_path(path)
|
||||
path_entries = defaultdict(PathEntry)
|
||||
if not path.name.lower() in ["scripts", "bin"]:
|
||||
bin_name = "Scripts" if os.name == "nt" else "bin"
|
||||
bin_dir = path / bin_name
|
||||
else:
|
||||
bin_dir = path
|
||||
current_entry = PathEntry.create(
|
||||
bin_dir, is_root=True, only_python=True, pythons=pythons
|
||||
)
|
||||
path_entries[bin_dir.as_posix()] = current_entry
|
||||
return cls(base=bin_dir, paths=path_entries)
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import attr
|
||||
from collections import defaultdict
|
||||
from . import BaseFinder
|
||||
from .path import VersionPath
|
||||
from .python import PythonVersion
|
||||
from ..utils import optional_instance_of, ensure_path
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
|
||||
@attr.s
|
||||
class PyenvFinder(BaseFinder):
|
||||
root = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
versions = attr.ib()
|
||||
|
||||
@versions.default
|
||||
def get_versions(self):
|
||||
versions = defaultdict(VersionPath)
|
||||
for p in self.root.glob("versions/*"):
|
||||
version = PythonVersion.parse(p.name)
|
||||
version_tuple = (
|
||||
version.get("major"),
|
||||
version.get("minor"),
|
||||
version.get("patch"),
|
||||
version.get("is_prerelease"),
|
||||
version.get("is_devrelease"),
|
||||
)
|
||||
versions[version_tuple] = VersionPath.create(path=p, only_python=True)
|
||||
return versions
|
||||
|
||||
@classmethod
|
||||
def create(cls, root):
|
||||
root = ensure_path(root)
|
||||
return cls(root=root)
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import attr
|
||||
import copy
|
||||
import platform
|
||||
from packaging.version import parse as parse_version, Version
|
||||
from ..environment import SYSTEM_ARCH
|
||||
from ..utils import _filter_none, optional_instance_of, get_python_version, ensure_path
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
|
||||
@attr.s
|
||||
class PythonVersion(object):
|
||||
major = attr.ib(default=0)
|
||||
minor = attr.ib(default=None)
|
||||
patch = attr.ib(default=None)
|
||||
is_prerelease = attr.ib(default=False)
|
||||
is_postrelease = attr.ib(default=False)
|
||||
is_devrelease = attr.ib(default=False)
|
||||
version = attr.ib(default=None, validator=optional_instance_of(Version))
|
||||
architecture = attr.ib(default=None)
|
||||
comes_from = attr.ib(default=None)
|
||||
executable = attr.ib(default=None)
|
||||
|
||||
@property
|
||||
def version_tuple(self):
|
||||
"""Provides a version tuple for using as a dictionary key.
|
||||
|
||||
:return: A tuple describing the python version meetadata contained.
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
return (
|
||||
self.major,
|
||||
self.minor,
|
||||
self.patch,
|
||||
self.is_prerelease,
|
||||
self.is_devrelease,
|
||||
)
|
||||
|
||||
def matches(self, major, minor=None, patch=None, pre=False, dev=False):
|
||||
return (
|
||||
self.major == major
|
||||
and (minor is None or self.minor == minor)
|
||||
and (patch is None or self.patch == patch)
|
||||
and (pre is None or self.is_prerelease == pre)
|
||||
and (dev is None or self.is_devrelease == dev)
|
||||
)
|
||||
|
||||
def as_major(self):
|
||||
self_dict = attr.asdict(self, recurse=False, filter=_filter_none).copy()
|
||||
self_dict.update({"minor": None, "patch": None})
|
||||
return self.create(**self_dict)
|
||||
|
||||
def as_minor(self):
|
||||
self_dict = attr.asdict(self, recurse=False, filter=_filter_none).copy()
|
||||
self_dict.update({"patch": None})
|
||||
return self.create(**self_dict)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, version):
|
||||
"""Parse a valid version string into a dictionary
|
||||
|
||||
Raises:
|
||||
ValueError -- Unable to parse version string
|
||||
ValueError -- Not a valid python version
|
||||
|
||||
:param version: A valid version string
|
||||
:type version: str
|
||||
:return: A dictionary with metadata about the specified python version.
|
||||
:rtype: dict.
|
||||
"""
|
||||
|
||||
try:
|
||||
version = parse_version(version)
|
||||
except TypeError:
|
||||
raise ValueError("Unable to parse version: %s" % version)
|
||||
if not version or not version.release:
|
||||
raise ValueError("Not a valid python version: %r" % version)
|
||||
return
|
||||
if len(version.release) >= 3:
|
||||
major, minor, patch = version.release[:3]
|
||||
elif len(version.release) == 2:
|
||||
major, minor = version.release
|
||||
patch = None
|
||||
else:
|
||||
major = version.release[0]
|
||||
minor = None
|
||||
patch = None
|
||||
return {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
"patch": patch,
|
||||
"is_prerelease": version.is_prerelease,
|
||||
"is_postrelease": version.is_postrelease,
|
||||
"is_devrelease": version.is_devrelease,
|
||||
"version": version,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_path(cls, path):
|
||||
"""Parses a python version from a system path.
|
||||
|
||||
Raises:
|
||||
ValueError -- Not a valid python path
|
||||
|
||||
:param path: A string or :class:`~pythonfinder.models.path.PathEntry`
|
||||
:type path: str or :class:`~pythonfinder.models.path.PathEntry` instance
|
||||
:param launcher_entry: A python launcher environment object.
|
||||
:return: An instance of a PythonVersion.
|
||||
:rtype: :class:`~pythonfinder.models.python.PythonVersion`
|
||||
"""
|
||||
|
||||
from .path import PathEntry
|
||||
|
||||
if not isinstance(path, PathEntry):
|
||||
path = PathEntry(path)
|
||||
if not path.is_python:
|
||||
raise ValueError("Not a valid python path: %s" % path.path)
|
||||
return
|
||||
py_version, _ = get_python_version(str(path.path))
|
||||
instance_dict = cls.parse(py_version)
|
||||
if not isinstance(instance_dict.get("version"), Version):
|
||||
raise ValueError("Not a valid python path: %s" % path.path)
|
||||
return
|
||||
architecture, _ = platform.architecture(path.path.as_posix())
|
||||
instance_dict.update({"comes_from": path, "architecture": architecture})
|
||||
return cls(**instance_dict)
|
||||
|
||||
@classmethod
|
||||
def from_windows_launcher(cls, launcher_entry):
|
||||
"""Create a new PythonVersion instance from a Windows Launcher Entry
|
||||
|
||||
:param launcher_entry: A python launcher environment object.
|
||||
:return: An instance of a PythonVersion.
|
||||
:rtype: :class:`~pythonfinder.models.python.PythonVersion`
|
||||
"""
|
||||
|
||||
from .path import PathEntry
|
||||
|
||||
creation_dict = cls.parse(launcher_entry.info.version)
|
||||
base_path = ensure_path(launcher_entry.info.install_path.__getattr__(""))
|
||||
default_path = base_path / "python.exe"
|
||||
if not default_path.exists():
|
||||
default_path = base_path / "Scripts" / "python.exe"
|
||||
exe_path = ensure_path(
|
||||
getattr(launcher_entry.info.install_path, "executable_path", default_path)
|
||||
)
|
||||
creation_dict.update(
|
||||
{
|
||||
"architecture": getattr(
|
||||
launcher_entry, "sys_architecture", SYSTEM_ARCH
|
||||
),
|
||||
"executable": exe_path,
|
||||
}
|
||||
)
|
||||
py_version = cls.create(**creation_dict)
|
||||
comes_from = PathEntry.create(exe_path, only_python=True)
|
||||
comes_from.py_version = copy.deepcopy(py_version)
|
||||
py_version.comes_from = comes_from
|
||||
return py_version
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
return cls(**kwargs)
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import attr
|
||||
import operator
|
||||
from collections import defaultdict
|
||||
from . import BaseFinder
|
||||
from .path import PathEntry
|
||||
from .python import PythonVersion
|
||||
from ..utils import ensure_path
|
||||
|
||||
|
||||
@attr.s
|
||||
class WindowsFinder(BaseFinder):
|
||||
paths = attr.ib(default=attr.Factory(list))
|
||||
version_list = attr.ib(default=attr.Factory(list))
|
||||
versions = attr.ib()
|
||||
|
||||
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches", major, minor=minor, patch=patch, pre=pre, dev=dev
|
||||
)
|
||||
py_filter = filter(
|
||||
None, filter(lambda c: version_matcher(c), self.version_list)
|
||||
)
|
||||
version_sort = operator.attrgetter("version")
|
||||
return next(
|
||||
(c.comes_from for c in sorted(py_filter, key=version_sort, reverse=True)), None
|
||||
)
|
||||
|
||||
@versions.default
|
||||
def get_versions(self):
|
||||
versions = defaultdict(PathEntry)
|
||||
from pythonfinder._vendor.pep514tools import environment as pep514env
|
||||
|
||||
env_versions = pep514env.findall()
|
||||
path = None
|
||||
for version_object in env_versions:
|
||||
path = ensure_path(version_object.info.install_path.__getattr__(""))
|
||||
py_version = PythonVersion.from_windows_launcher(version_object)
|
||||
self.version_list.append(py_version)
|
||||
base_dir = PathEntry.create(
|
||||
path,
|
||||
is_root=True,
|
||||
only_python=True,
|
||||
pythons={py_version.comes_from.path: py_version},
|
||||
)
|
||||
versions[py_version.version_tuple[:5]] = base_dir
|
||||
self.paths.append(base_dir)
|
||||
return versions
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
return cls()
|
||||
+49
-224
@@ -1,234 +1,59 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import delegator
|
||||
import platform
|
||||
from packaging.version import parse as parse_version
|
||||
from collections import defaultdict
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
import six
|
||||
from .models import SystemPath
|
||||
|
||||
|
||||
PYENV_INSTALLED = (bool(os.environ.get('PYENV_SHELL')) or bool(os.environ.get('PYENV_ROOT')))
|
||||
PYENV_ROOT = os.environ.get('PYENV_ROOT', os.path.expanduser('~/.pyenv'))
|
||||
IS_64BIT_OS = None
|
||||
SYSTEM_ARCH = platform.architecture()[0]
|
||||
class Finder(object):
|
||||
def __init__(self, path=None, system=False):
|
||||
self.path_prepend = path
|
||||
self.system = system
|
||||
self._system_path = None
|
||||
self._windows_finder = None
|
||||
|
||||
if sys.maxsize > 2**32:
|
||||
IS_64BIT_OS = (platform.machine() == 'AMD64')
|
||||
else:
|
||||
IS_64BIT_OS = False
|
||||
@property
|
||||
def system_path(self):
|
||||
if not self._system_path:
|
||||
self._system_path = SystemPath.create(
|
||||
path=self.path_prepend, system=self.system
|
||||
)
|
||||
return self._system_path
|
||||
|
||||
@property
|
||||
def windows_finder(self):
|
||||
if os.name == "nt" and not self._windows_finder:
|
||||
from .models import WindowsFinder
|
||||
|
||||
def shellquote(s):
|
||||
"""Prepares a string for the shell (on Windows too!)
|
||||
self._windows_finder = WindowsFinder()
|
||||
return self._windows_finder
|
||||
|
||||
Only for use on grouped arguments (passed as a string to Popen)
|
||||
"""
|
||||
if s is None:
|
||||
return None
|
||||
# Additional escaping for windows paths
|
||||
if os.name == 'nt':
|
||||
s = "{}".format(s.replace("\\", "\\\\"))
|
||||
def which(self, exe):
|
||||
return self.system_path.which(exe)
|
||||
|
||||
return '"' + s.replace("'", "'\\''") + '"'
|
||||
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
|
||||
if (
|
||||
major
|
||||
and not minor
|
||||
and not patch
|
||||
and not pre
|
||||
and not dev
|
||||
and isinstance(major, six.string_types)
|
||||
):
|
||||
if "." in major:
|
||||
from .models import PythonVersion
|
||||
|
||||
|
||||
class PathFinder(object):
|
||||
WHICH = {}
|
||||
|
||||
def __init__(self, path=None):
|
||||
self.path = path if path else os.environ.get('PATH')
|
||||
self._populate_which_dict()
|
||||
|
||||
@classmethod
|
||||
def which(cls, cmd):
|
||||
if not cls.WHICH:
|
||||
cls._populate_which_dict()
|
||||
return cls.WHICH.get(cmd, cmd)
|
||||
|
||||
@classmethod
|
||||
def _populate_which_dict(cls):
|
||||
for path in os.environ.get('PATH', '').split(os.pathsep):
|
||||
path = os.path.expandvars(path)
|
||||
files = os.listdir(path)
|
||||
for fn in files:
|
||||
full_path = os.sep.join([path, fn])
|
||||
if os.access(full_path, os.X_OK) and not os.path.isdir(full_path):
|
||||
if not cls.WHICH.get(fn):
|
||||
cls.WHICH[fn] = full_path
|
||||
base_exec = os.path.splitext(fn)[0]
|
||||
if not cls.WHICH.get(base_exec):
|
||||
cls.WHICH[base_exec] = full_path
|
||||
|
||||
|
||||
class PythonFinder(PathFinder):
|
||||
"""Find pythons given a specific version, path, or nothing."""
|
||||
|
||||
PYENV_VERSIONS = {}
|
||||
PYTHON_VERSIONS = {}
|
||||
PYTHON_PATHS = {}
|
||||
MAX_PYTHON = {}
|
||||
PYTHON_ARCHS = defaultdict(dict)
|
||||
WHICH_PYTHON = {}
|
||||
RUNTIMES = ['python', 'pypy', 'ipy', 'jython', 'pyston']
|
||||
|
||||
def __init__(self, path=None, version=None, full_version=None):
|
||||
self.version = version
|
||||
self.full_version = full_version
|
||||
super(PythonFinder, self).__init__(path=path)
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, python):
|
||||
if os.path.isabs(python) and os.access(python, os.X_OK):
|
||||
return python
|
||||
if python.startswith('py'):
|
||||
windows_finder = python.split()
|
||||
if len(windows_finder) > 1 and windows_finder[0] == 'py' and windows_finder[1].startswith('-'):
|
||||
version = windows_finder[1].strip('-').split()[0]
|
||||
return cls.from_version(version)
|
||||
return cls.WHICH_PYTHON.get(python) or cls.which(python)
|
||||
|
||||
@classmethod
|
||||
def from_version(cls, version, architecture=None):
|
||||
guess = cls.PYTHON_VERSIONS.get(cls.MAX_PYTHON.get(version, version))
|
||||
if guess:
|
||||
return guess
|
||||
if os.name == 'nt':
|
||||
path = cls.from_windows_finder(version, architecture)
|
||||
else:
|
||||
parsed_version = parse_version(version)
|
||||
full_version = parsed_version.base_version
|
||||
if PYENV_INSTALLED:
|
||||
path = cls.from_pyenv(full_version)
|
||||
else:
|
||||
path = cls._crawl_path_for_version(full_version)
|
||||
if path and not isinstance(path, Path):
|
||||
path = Path(path)
|
||||
return path
|
||||
|
||||
@classmethod
|
||||
def from_windows_finder(cls, version=None, arch=None):
|
||||
if not cls.PYTHON_VERSIONS:
|
||||
cls._populate_windows_python_versions()
|
||||
if arch:
|
||||
return cls.PYTHON_ARCHS[version][arch]
|
||||
return cls.PYTHON_VERSIONS[version]
|
||||
|
||||
@classmethod
|
||||
def _populate_windows_python_versions(cls):
|
||||
from pythonfinder._vendor.pep514tools import environment
|
||||
versions = environment.findall()
|
||||
path = None
|
||||
for version_object in versions:
|
||||
path = Path(version_object.info.install_path.__getattr__('')).joinpath('python.exe')
|
||||
version = version_object.info.sys_version
|
||||
full_version = version_object.info.version
|
||||
architecture = getattr(version_object, 'sys_architecture', SYSTEM_ARCH)
|
||||
for v in [version, full_version, architecture]:
|
||||
if not cls.PYTHON_VERSIONS.get(v):
|
||||
cls.PYTHON_VERSIONS[v] = '{0}'.format(path)
|
||||
cls.register_python(path, full_version, architecture)
|
||||
|
||||
@classmethod
|
||||
def _populate_python_versions(cls):
|
||||
import fnmatch
|
||||
match_rules = ['*python', '*python?', '*python?.?', '*python?.?m']
|
||||
runtime_execs = []
|
||||
exts = list(filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)))
|
||||
for path in os.environ.get('PATH', '').split(os.pathsep):
|
||||
path = os.path.expandvars(path)
|
||||
from glob import glob
|
||||
pythons = glob(os.sep.join([path, 'python*']))
|
||||
execs = [match for rule in match_rules for match in fnmatch.filter(pythons, rule)]
|
||||
for executable in execs:
|
||||
exec_name = os.path.basename(executable)
|
||||
if os.access(executable, os.X_OK):
|
||||
runtime_execs.append(executable)
|
||||
if not cls.WHICH_PYTHON.get(exec_name):
|
||||
cls.WHICH_PYTHON[exec_name] = executable
|
||||
for e in exts:
|
||||
pext = executable + e
|
||||
if os.access(pext, os.X_OK):
|
||||
runtime_execs.append(pext)
|
||||
for python in runtime_execs:
|
||||
version_cmd = '{0} -c "import sys; print(sys.version.split()[0])"'.format(shellquote(python))
|
||||
version = delegator.run(version_cmd).out.strip()
|
||||
cls.register_python(python, version)
|
||||
|
||||
@classmethod
|
||||
def _crawl_path_for_version(cls, version):
|
||||
if not cls.PYTHON_VERSIONS:
|
||||
cls._populate_python_versions()
|
||||
return cls.PYTHON_VERSIONS.get(version)
|
||||
|
||||
@classmethod
|
||||
def from_pyenv(cls, version):
|
||||
if not cls.PYENV_VERSIONS:
|
||||
cls.populate_pyenv_runtimes()
|
||||
return cls.PYENV_VERSIONS[version]
|
||||
|
||||
@classmethod
|
||||
def register_python(cls, path, full_version, pre=False, pyenv=False, arch=None):
|
||||
if not arch:
|
||||
import platform
|
||||
arch, _ = platform.architecture()
|
||||
parsed_version = parse_version(full_version)
|
||||
if isinstance(parsed_version._version, str):
|
||||
if arch == SYSTEM_ARCH or SYSTEM_ARCH.startswith(str(arch)):
|
||||
cls.PYTHON_VERSIONS[parsed_version._version] = path
|
||||
cls.MAX_PYTHON[parsed_version._version] = parsed_version._version
|
||||
cls.PYTHON_VERSION[parsed_version._version] = parsed_version._version
|
||||
cls.PYTHON_PATHS[path] = parsed_version._version
|
||||
cls.PYTHON_ARCHS[parsed_version._version][arch] = path
|
||||
return
|
||||
pre = pre or parsed_version.is_prerelease
|
||||
major_minor = '.'.join(['{0}'.format(v) for v in parsed_version._version.release[:2]])
|
||||
major = '{0}'.format(parsed_version._version.release[0])
|
||||
cls.PYTHON_PATHS[path] = full_version
|
||||
if not pre and parsed_version > parse_version(cls.MAX_PYTHON.get(major_minor, '0.0.0')):
|
||||
if major_minor != full_version:
|
||||
if parsed_version > parse_version(cls.MAX_PYTHON.get(full_version, '0.0.0')):
|
||||
cls.MAX_PYTHON[full_version] = parsed_version.base_version
|
||||
cls.MAX_PYTHON[major_minor] = parsed_version.base_version
|
||||
cls.PYTHON_VERSIONS[major_minor] = path
|
||||
if arch == SYSTEM_ARCH or SYSTEM_ARCH.startswith(str(arch)):
|
||||
if parsed_version > parse_version(cls.MAX_PYTHON.get(major, '0.0.0')):
|
||||
cls.MAX_PYTHON[major] = parsed_version.base_version
|
||||
cls.PYTHON_VERSIONS[major] = path
|
||||
if not pyenv:
|
||||
for v in [full_version, major_minor, major]:
|
||||
if not cls.PYTHON_VERSIONS.get(v) or cls.MAX_PYTHON.get(v) == full_version:
|
||||
if cls.MAX_PYTHON.get(v) == full_version and not (arch == SYSTEM_ARCH or SYSTEM_ARCH.startswith(str(arch))):
|
||||
pass
|
||||
else:
|
||||
cls.PYTHON_VERSIONS[v] = path
|
||||
if not cls.PYTHON_ARCHS.get(v, {}).get(arch):
|
||||
cls.PYTHON_ARCHS[v][arch] = path
|
||||
else:
|
||||
for v in [full_version, major_minor, major]:
|
||||
if (not cls.PYENV_VERSIONS.get(v) and (v == major and not pre) or v != major) or cls.MAX_PYTHON.get(v) == full_version:
|
||||
cls.PYENV_VERSIONS[v] = path
|
||||
if not cls.PYTHON_VERSIONS.get(full_version):
|
||||
cls.PYTHON_VERSIONS[full_version] = path
|
||||
if not cls.PYTHON_ARCHS.get(v, {}).get(arch):
|
||||
cls.PYTHON_ARCHS[v][arch] = path
|
||||
|
||||
@classmethod
|
||||
def populate_pyenv_runtimes(cls):
|
||||
from glob import glob
|
||||
search_path = os.sep.join(['{0}'.format(PYENV_ROOT), 'versions', '*'])
|
||||
runtimes = ['pypy', 'ipy', 'jython', 'pyston']
|
||||
for pyenv_path in glob(search_path):
|
||||
parsed_version = parse_version(os.path.basename(pyenv_path))
|
||||
if parsed_version.is_prerelease and cls.PYENV_VERSIONS.get(parsed_version.base_version):
|
||||
continue
|
||||
bin_path = os.sep.join([pyenv_path, 'bin'])
|
||||
runtime = os.sep.join([bin_path, 'python'])
|
||||
if not os.path.exists(runtime):
|
||||
exes = [os.sep.join([bin_path, exe]) for exe in runtimes if os.path.exists(os.sep.join([bin_path, exe]))]
|
||||
if exes:
|
||||
runtime = exes[0]
|
||||
cls.register_python(runtime, parsed_version.base_version, pre=parsed_version.is_prerelease, pyenv=True)
|
||||
version_dict = PythonVersion.parse(major)
|
||||
major = version_dict["major"]
|
||||
minor = version_dict["minor"]
|
||||
patch = version_dict["patch"]
|
||||
pre = version_dict["is_prerelease"]
|
||||
dev = version_dict["is_devrelease"]
|
||||
if os.name == "nt":
|
||||
match = self.windows_finder.find_python_version(
|
||||
major, minor=minor, patch=patch, pre=pre, dev=dev
|
||||
)
|
||||
if match:
|
||||
return match
|
||||
return self.system_path.find_python_version(
|
||||
major, minor=minor, patch=patch, pre=pre, dev=dev
|
||||
)
|
||||
|
||||
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import attr
|
||||
import locale
|
||||
import os
|
||||
import subprocess
|
||||
from fnmatch import fnmatch
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
|
||||
PYTHON_IMPLEMENTATIONS = ("python", "ironpython", "jython", "pypy")
|
||||
|
||||
KNOWN_EXTS = {"exe", "py", "fish", "sh", ""}
|
||||
KNOWN_EXTS = KNOWN_EXTS | set(
|
||||
filter(None, os.environ.get("PATHEXT", "").split(os.pathsep))
|
||||
)
|
||||
|
||||
|
||||
def _run(cmd):
|
||||
"""Use `subprocess.check_output` to get the output of a command and decode it.
|
||||
|
||||
:param list cmd: A list representing the command you want to run.
|
||||
:returns: A 2-tuple of (output, error)
|
||||
"""
|
||||
encoding = locale.getdefaultlocale()[1] or "utf-8"
|
||||
env = os.environ.copy()
|
||||
c = subprocess.Popen(
|
||||
cmd,
|
||||
encoding=encoding,
|
||||
env=env,
|
||||
universal_newlines=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
output, err = c.communicate()
|
||||
return output.strip(), err.strip()
|
||||
|
||||
|
||||
def get_python_version(path):
|
||||
"""Get python version string using subprocess from a given path."""
|
||||
version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"]
|
||||
return _run(version_cmd)
|
||||
|
||||
|
||||
def optional_instance_of(cls):
|
||||
return attr.validators.optional(attr.validators.instance_of(cls))
|
||||
|
||||
|
||||
def path_and_exists(path):
|
||||
return attr.validators.instance_of(Path) and path.exists()
|
||||
|
||||
|
||||
def path_is_executable(path):
|
||||
return os.access(str(path), os.X_OK)
|
||||
|
||||
|
||||
def path_is_known_executable(path):
|
||||
return (
|
||||
path_is_executable(path)
|
||||
or os.access(str(path), os.R_OK)
|
||||
and path.suffix in KNOWN_EXTS
|
||||
)
|
||||
|
||||
|
||||
def is_python_name(name):
|
||||
rules = ["*python", "*python?", "*python?.?", "*python?.?m"]
|
||||
match_rules = []
|
||||
for rule in rules:
|
||||
match_rules.extend(
|
||||
[
|
||||
"{0}.{1}".format(rule, ext) if ext else "{0}".format(rule)
|
||||
for ext in KNOWN_EXTS
|
||||
]
|
||||
)
|
||||
if not any(name.lower().startswith(py_name) for py_name in PYTHON_IMPLEMENTATIONS):
|
||||
return False
|
||||
return any(fnmatch(name, rule) for rule in match_rules)
|
||||
|
||||
|
||||
def path_is_python(path):
|
||||
return path_is_executable(path) and is_python_name(path.name)
|
||||
|
||||
|
||||
def ensure_path(path):
|
||||
"""Given a path (either a string or a Path object), expand variables and return a Path object.
|
||||
|
||||
:param path: A string or a :class:`~pathlib.Path` object.
|
||||
:type path: str or :class:`~pathlib.Path`
|
||||
:return: A fully expanded Path object.
|
||||
:rtype: :class:`~pathlib.Path`
|
||||
"""
|
||||
|
||||
if isinstance(path, Path):
|
||||
return Path(os.path.expandvars(path.as_posix()))
|
||||
return Path(os.path.expandvars(path))
|
||||
|
||||
|
||||
def _filter_none(k, v):
|
||||
if v:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def filter_pythons(path):
|
||||
"""Return all valid pythons in a given path"""
|
||||
if not isinstance(path, Path):
|
||||
path = Path(str(path))
|
||||
if not path.is_dir():
|
||||
return path if path_is_python(path) else None
|
||||
return filter(lambda x: path_is_python(x), path.iterdir())
|
||||
Vendored
+16
-16
@@ -1,16 +1,16 @@
|
||||
No-notice MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
No-notice MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Vendored
+26
-26
@@ -6,35 +6,35 @@ if sys.version_info[0] == 2:
|
||||
else:
|
||||
_chr = chr
|
||||
|
||||
def load(fin, translate=lambda t, x, v: v):
|
||||
return loads(fin.read(), translate=translate, filename=getattr(fin, 'name', repr(fin)))
|
||||
def load(fin, translate=lambda t, x, v: v, object_pairs_hook=dict):
|
||||
return loads(fin.read(), translate=translate, object_pairs_hook=object_pairs_hook, filename=getattr(fin, 'name', repr(fin)))
|
||||
|
||||
def loads(s, filename='<string>', translate=lambda t, x, v: v):
|
||||
def loads(s, filename='<string>', translate=lambda t, x, v: v, object_pairs_hook=dict):
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode('utf-8')
|
||||
|
||||
s = s.replace('\r\n', '\n')
|
||||
|
||||
root = {}
|
||||
tables = {}
|
||||
root = object_pairs_hook()
|
||||
tables = object_pairs_hook()
|
||||
scope = root
|
||||
|
||||
src = _Source(s, filename=filename)
|
||||
ast = _p_toml(src)
|
||||
ast = _p_toml(src, object_pairs_hook=object_pairs_hook)
|
||||
|
||||
def error(msg):
|
||||
raise TomlError(msg, pos[0], pos[1], filename)
|
||||
|
||||
def process_value(v):
|
||||
def process_value(v, object_pairs_hook):
|
||||
kind, text, value, pos = v
|
||||
if kind == 'str' and value.startswith('\n'):
|
||||
value = value[1:]
|
||||
if kind == 'array':
|
||||
if value and any(k != value[0][0] for k, t, v, p in value[1:]):
|
||||
error('array-type-mismatch')
|
||||
value = [process_value(item) for item in value]
|
||||
value = [process_value(item, object_pairs_hook=object_pairs_hook) for item in value]
|
||||
elif kind == 'table':
|
||||
value = dict([(k, process_value(value[k])) for k in value])
|
||||
value = object_pairs_hook([(k, process_value(value[k], object_pairs_hook=object_pairs_hook)) for k in value])
|
||||
return translate(kind, text, value)
|
||||
|
||||
for kind, value, pos in ast:
|
||||
@@ -42,7 +42,7 @@ def loads(s, filename='<string>', translate=lambda t, x, v: v):
|
||||
k, v = value
|
||||
if k in scope:
|
||||
error('duplicate_keys. Key "{0}" was used more than once.'.format(k))
|
||||
scope[k] = process_value(v)
|
||||
scope[k] = process_value(v, object_pairs_hook=object_pairs_hook)
|
||||
else:
|
||||
is_table_array = (kind == 'table_array')
|
||||
cur = tables
|
||||
@@ -50,19 +50,19 @@ def loads(s, filename='<string>', translate=lambda t, x, v: v):
|
||||
if isinstance(cur.get(name), list):
|
||||
d, cur = cur[name][-1]
|
||||
else:
|
||||
d, cur = cur.setdefault(name, (None, {}))
|
||||
d, cur = cur.setdefault(name, (None, object_pairs_hook()))
|
||||
|
||||
scope = {}
|
||||
scope = object_pairs_hook()
|
||||
name = value[-1]
|
||||
if name not in cur:
|
||||
if is_table_array:
|
||||
cur[name] = [(scope, {})]
|
||||
cur[name] = [(scope, object_pairs_hook())]
|
||||
else:
|
||||
cur[name] = (scope, {})
|
||||
cur[name] = (scope, object_pairs_hook())
|
||||
elif isinstance(cur[name], list):
|
||||
if not is_table_array:
|
||||
error('table_type_mismatch')
|
||||
cur[name].append((scope, {}))
|
||||
cur[name].append((scope, object_pairs_hook()))
|
||||
else:
|
||||
if is_table_array:
|
||||
error('table_type_mismatch')
|
||||
@@ -73,7 +73,7 @@ def loads(s, filename='<string>', translate=lambda t, x, v: v):
|
||||
|
||||
def merge_tables(scope, tables):
|
||||
if scope is None:
|
||||
scope = {}
|
||||
scope = object_pairs_hook()
|
||||
for k in tables:
|
||||
if k in scope:
|
||||
error('key_table_conflict')
|
||||
@@ -225,7 +225,7 @@ _datetime_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d
|
||||
_basicstr_ml_re = re.compile(r'(?:(?:|"|"")[^"\\\000-\011\013-\037])*')
|
||||
_litstr_re = re.compile(r"[^'\000-\037]*")
|
||||
_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\011\013-\037]))*")
|
||||
def _p_value(s):
|
||||
def _p_value(s, object_pairs_hook):
|
||||
pos = s.pos()
|
||||
|
||||
if s.consume('true'):
|
||||
@@ -283,7 +283,7 @@ def _p_value(s):
|
||||
with s:
|
||||
while True:
|
||||
_p_ews(s)
|
||||
items.append(_p_value(s))
|
||||
items.append(_p_value(s, object_pairs_hook=object_pairs_hook))
|
||||
s.commit()
|
||||
_p_ews(s)
|
||||
s.expect(',')
|
||||
@@ -294,13 +294,13 @@ def _p_value(s):
|
||||
|
||||
if s.consume('{'):
|
||||
_p_ws(s)
|
||||
items = {}
|
||||
items = object_pairs_hook()
|
||||
if not s.consume('}'):
|
||||
k = _p_key(s)
|
||||
_p_ws(s)
|
||||
s.expect('=')
|
||||
_p_ws(s)
|
||||
items[k] = _p_value(s)
|
||||
items[k] = _p_value(s, object_pairs_hook=object_pairs_hook)
|
||||
_p_ws(s)
|
||||
while s.consume(','):
|
||||
_p_ws(s)
|
||||
@@ -308,14 +308,14 @@ def _p_value(s):
|
||||
_p_ws(s)
|
||||
s.expect('=')
|
||||
_p_ws(s)
|
||||
items[k] = _p_value(s)
|
||||
items[k] = _p_value(s, object_pairs_hook=object_pairs_hook)
|
||||
_p_ws(s)
|
||||
s.expect('}')
|
||||
return 'table', None, items, pos
|
||||
|
||||
s.fail()
|
||||
|
||||
def _p_stmt(s):
|
||||
def _p_stmt(s, object_pairs_hook):
|
||||
pos = s.pos()
|
||||
if s.consume( '['):
|
||||
is_array = s.consume('[')
|
||||
@@ -335,19 +335,19 @@ def _p_stmt(s):
|
||||
_p_ws(s)
|
||||
s.expect('=')
|
||||
_p_ws(s)
|
||||
value = _p_value(s)
|
||||
value = _p_value(s, object_pairs_hook=object_pairs_hook)
|
||||
return 'kv', (key, value), pos
|
||||
|
||||
_stmtsep_re = re.compile(r'(?:[ \t]*(?:#[^\n]*)?\n)+[ \t]*')
|
||||
def _p_toml(s):
|
||||
def _p_toml(s, object_pairs_hook):
|
||||
stmts = []
|
||||
_p_ews(s)
|
||||
with s:
|
||||
stmts.append(_p_stmt(s))
|
||||
stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook))
|
||||
while True:
|
||||
s.commit()
|
||||
s.expect_re(_stmtsep_re)
|
||||
stmts.append(_p_stmt(s))
|
||||
stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook))
|
||||
_p_ews(s)
|
||||
s.expect_eof()
|
||||
return stmts
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2017 Kenneth Reitz
|
||||
Copyright 2018 Kenneth Reitz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
Vendored
+17
-2
@@ -57,10 +57,10 @@ def check_compatibility(urllib3_version, chardet_version):
|
||||
# Check urllib3 for compatibility.
|
||||
major, minor, patch = urllib3_version # noqa: F811
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# urllib3 >= 1.21.1, <= 1.22
|
||||
# urllib3 >= 1.21.1, <= 1.23
|
||||
assert major == 1
|
||||
assert minor >= 21
|
||||
assert minor <= 22
|
||||
assert minor <= 23
|
||||
|
||||
# Check chardet for compatibility.
|
||||
major, minor, patch = chardet_version.split('.')[:3]
|
||||
@@ -71,6 +71,17 @@ def check_compatibility(urllib3_version, chardet_version):
|
||||
assert patch >= 2
|
||||
|
||||
|
||||
def _check_cryptography(cryptography_version):
|
||||
# cryptography < 1.3.4
|
||||
try:
|
||||
cryptography_version = list(map(int, cryptography_version.split('.')))
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
if cryptography_version < [1, 3, 4]:
|
||||
warning = 'Old version of cryptography ({0}) may cause slowdown.'.format(cryptography_version)
|
||||
warnings.warn(warning, RequestsDependencyWarning)
|
||||
|
||||
# Check imported dependencies for compatibility.
|
||||
try:
|
||||
check_compatibility(urllib3.__version__, chardet.__version__)
|
||||
@@ -83,6 +94,10 @@ except (AssertionError, ValueError):
|
||||
try:
|
||||
from urllib3.contrib import pyopenssl
|
||||
pyopenssl.inject_into_urllib3()
|
||||
|
||||
# Check cryptography version
|
||||
from cryptography import __version__ as cryptography_version
|
||||
_check_cryptography(cryptography_version)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
+3
-3
@@ -5,10 +5,10 @@
|
||||
__title__ = 'requests'
|
||||
__description__ = 'Python HTTP for Humans.'
|
||||
__url__ = 'http://python-requests.org'
|
||||
__version__ = '2.18.4'
|
||||
__build__ = 0x021804
|
||||
__version__ = '2.19.1'
|
||||
__build__ = 0x021901
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__author_email__ = 'me@kennethreitz.org'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2017 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2018 Kenneth Reitz'
|
||||
__cake__ = u'\u2728 \U0001f370 \u2728'
|
||||
|
||||
Vendored
+11
-6
@@ -13,6 +13,7 @@ import socket
|
||||
|
||||
from urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from urllib3.response import HTTPResponse
|
||||
from urllib3.util import parse_url
|
||||
from urllib3.util import Timeout as TimeoutSauce
|
||||
from urllib3.util.retry import Retry
|
||||
from urllib3.exceptions import ClosedPoolError
|
||||
@@ -28,13 +29,13 @@ from urllib3.exceptions import ResponseError
|
||||
|
||||
from .models import Response
|
||||
from .compat import urlparse, basestring
|
||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
|
||||
select_proxy)
|
||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths,
|
||||
get_encoding_from_headers, prepend_scheme_if_needed,
|
||||
get_auth_from_url, urldefragauth, select_proxy)
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
||||
ProxyError, RetryError, InvalidSchema)
|
||||
ProxyError, RetryError, InvalidSchema, InvalidProxyURL)
|
||||
from .auth import _basic_auth_str
|
||||
|
||||
try:
|
||||
@@ -219,7 +220,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
cert_loc = verify
|
||||
|
||||
if not cert_loc:
|
||||
cert_loc = DEFAULT_CA_BUNDLE_PATH
|
||||
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
|
||||
|
||||
if not cert_loc or not os.path.exists(cert_loc):
|
||||
raise IOError("Could not find a suitable TLS CA certificate bundle, "
|
||||
@@ -300,6 +301,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
if proxy:
|
||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||
proxy_url = parse_url(proxy)
|
||||
if not proxy_url.host:
|
||||
raise InvalidProxyURL("Please check proxy URL. It is malformed"
|
||||
" and could be missing the host.")
|
||||
proxy_manager = self.proxy_manager_for(proxy)
|
||||
conn = proxy_manager.connection_from_url(url)
|
||||
else:
|
||||
@@ -406,7 +411,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
self.cert_verify(conn, request.url, verify, cert)
|
||||
url = self.request_url(request, proxies)
|
||||
self.add_headers(request)
|
||||
self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
|
||||
|
||||
chunked = not (request.body is None or 'Content-Length' in request.headers)
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -20,7 +20,7 @@ def request(method, url, **kwargs):
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
|
||||
|
||||
Vendored
+12
@@ -153,6 +153,18 @@ class HTTPDigestAuth(AuthBase):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
hash_utf8 = sha_utf8
|
||||
elif _algorithm == 'SHA-256':
|
||||
def sha256_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha256(x).hexdigest()
|
||||
hash_utf8 = sha256_utf8
|
||||
elif _algorithm == 'SHA-512':
|
||||
def sha512_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha512(x).hexdigest()
|
||||
hash_utf8 = sha512_utf8
|
||||
|
||||
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
|
||||
|
||||
|
||||
Vendored
+2
@@ -43,6 +43,7 @@ if is_py2:
|
||||
import cookielib
|
||||
from Cookie import Morsel
|
||||
from StringIO import StringIO
|
||||
from collections import Callable, Mapping, MutableMapping
|
||||
|
||||
from urllib3.packages.ordered_dict import OrderedDict
|
||||
|
||||
@@ -60,6 +61,7 @@ elif is_py3:
|
||||
from http.cookies import Morsel
|
||||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Callable, Mapping, MutableMapping
|
||||
|
||||
builtin_str = str
|
||||
str = str
|
||||
|
||||
Vendored
+7
-3
@@ -12,10 +12,9 @@ requests.utils imports from here, so be careful with imports.
|
||||
import copy
|
||||
import time
|
||||
import calendar
|
||||
import collections
|
||||
|
||||
from ._internal_utils import to_native_string
|
||||
from .compat import cookielib, urlparse, urlunparse, Morsel
|
||||
from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping
|
||||
|
||||
try:
|
||||
import threading
|
||||
@@ -169,7 +168,7 @@ class CookieConflictError(RuntimeError):
|
||||
"""
|
||||
|
||||
|
||||
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
|
||||
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
|
||||
interface.
|
||||
|
||||
@@ -415,9 +414,14 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
def copy(self):
|
||||
"""Return a copy of this RequestsCookieJar."""
|
||||
new_cj = RequestsCookieJar()
|
||||
new_cj.set_policy(self.get_policy())
|
||||
new_cj.update(self)
|
||||
return new_cj
|
||||
|
||||
def get_policy(self):
|
||||
"""Return the CookiePolicy instance used."""
|
||||
return self._policy
|
||||
|
||||
|
||||
def _copy_cookie_jar(jar):
|
||||
if jar is None:
|
||||
|
||||
Vendored
+4
@@ -85,6 +85,10 @@ class InvalidHeader(RequestException, ValueError):
|
||||
"""The header value provided was somehow invalid."""
|
||||
|
||||
|
||||
class InvalidProxyURL(InvalidURL):
|
||||
"""The proxy URL provided is invalid."""
|
||||
|
||||
|
||||
class ChunkedEncodingError(RequestException):
|
||||
"""The server declared chunked encoding but sent an invalid chunk."""
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@ import chardet
|
||||
from . import __version__ as requests_version
|
||||
|
||||
try:
|
||||
from .packages.urllib3.contrib import pyopenssl
|
||||
from urllib3.contrib import pyopenssl
|
||||
except ImportError:
|
||||
pyopenssl = None
|
||||
OpenSSL = None
|
||||
|
||||
Vendored
+12
-8
@@ -7,7 +7,6 @@ requests.models
|
||||
This module contains the primary objects that power Requests.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
@@ -37,6 +36,7 @@ from .utils import (
|
||||
stream_decode_response_unicode, to_key_val_list, parse_header_links,
|
||||
iter_slices, guess_json_utf, super_len, check_header_validity)
|
||||
from .compat import (
|
||||
Callable, Mapping,
|
||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
|
||||
is_py2, chardet, builtin_str, basestring)
|
||||
from .compat import json as complexjson
|
||||
@@ -155,8 +155,12 @@ class RequestEncodingMixin(object):
|
||||
|
||||
if isinstance(fp, (str, bytes, bytearray)):
|
||||
fdata = fp
|
||||
else:
|
||||
elif hasattr(fp, 'read'):
|
||||
fdata = fp.read()
|
||||
elif fp is None:
|
||||
continue
|
||||
else:
|
||||
fdata = fp
|
||||
|
||||
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
|
||||
rf.make_multipart(content_type=ft)
|
||||
@@ -174,10 +178,10 @@ class RequestHooksMixin(object):
|
||||
if event not in self.hooks:
|
||||
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
|
||||
|
||||
if isinstance(hook, collections.Callable):
|
||||
if isinstance(hook, Callable):
|
||||
self.hooks[event].append(hook)
|
||||
elif hasattr(hook, '__iter__'):
|
||||
self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable))
|
||||
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
|
||||
|
||||
def deregister_hook(self, event, hook):
|
||||
"""Deregister a previously registered hook.
|
||||
@@ -461,7 +465,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
|
||||
is_stream = all([
|
||||
hasattr(data, '__iter__'),
|
||||
not isinstance(data, (basestring, list, tuple, collections.Mapping))
|
||||
not isinstance(data, (basestring, list, tuple, Mapping))
|
||||
])
|
||||
|
||||
try:
|
||||
@@ -686,11 +690,11 @@ class Response(object):
|
||||
|
||||
@property
|
||||
def ok(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
"""Returns True if :attr:`status_code` is less than 400, False if not.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
the status code is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
try:
|
||||
@@ -820,7 +824,7 @@ class Response(object):
|
||||
if self.status_code == 0 or self.raw is None:
|
||||
self._content = None
|
||||
else:
|
||||
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
|
||||
self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
|
||||
|
||||
self._content_consumed = True
|
||||
# don't need to release the connection; that's been handled by urllib3
|
||||
|
||||
Vendored
+11
-7
@@ -8,13 +8,12 @@ This module provides a Session object to manage and persist settings across
|
||||
requests (cookies, auth, proxies).
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
from collections import Mapping
|
||||
from datetime import timedelta
|
||||
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse
|
||||
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping
|
||||
from .cookies import (
|
||||
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
|
||||
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
|
||||
@@ -38,8 +37,8 @@ from .status_codes import codes
|
||||
from .models import REDIRECT_STATI
|
||||
|
||||
# Preferred clock, based on which one is more accurate on a given system.
|
||||
if platform.system() == 'Windows':
|
||||
try: # Python 3.3+
|
||||
if sys.platform == 'win32':
|
||||
try: # Python 3.4+
|
||||
preferred_clock = time.perf_counter
|
||||
except AttributeError: # Earlier than Python 3.
|
||||
preferred_clock = time.clock
|
||||
@@ -123,6 +122,7 @@ class SessionRedirectMixin(object):
|
||||
hist = [] # keep track of history
|
||||
|
||||
url = self.get_redirect_target(resp)
|
||||
previous_fragment = urlparse(req.url).fragment
|
||||
while url:
|
||||
prepared_request = req.copy()
|
||||
|
||||
@@ -147,8 +147,12 @@ class SessionRedirectMixin(object):
|
||||
parsed_rurl = urlparse(resp.url)
|
||||
url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
|
||||
|
||||
# The scheme should be lower case...
|
||||
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
|
||||
parsed = urlparse(url)
|
||||
if parsed.fragment == '' and previous_fragment:
|
||||
parsed = parsed._replace(fragment=previous_fragment)
|
||||
elif parsed.fragment:
|
||||
previous_fragment = parsed.fragment
|
||||
url = parsed.geturl()
|
||||
|
||||
# Facilitate relative 'location' headers, as allowed by RFC 7231.
|
||||
@@ -696,7 +700,7 @@ class Session(SessionRedirectMixin):
|
||||
"""
|
||||
for (prefix, adapter) in self.adapters.items():
|
||||
|
||||
if url.lower().startswith(prefix):
|
||||
if url.lower().startswith(prefix.lower()):
|
||||
return adapter
|
||||
|
||||
# Nothing matches :-/
|
||||
|
||||
+34
-5
@@ -1,5 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The ``codes`` object defines a mapping from common names for HTTP statuses
|
||||
to their numerical codes, accessible either as attributes or as dictionary
|
||||
items.
|
||||
|
||||
>>> requests.codes['temporary_redirect']
|
||||
307
|
||||
>>> requests.codes.teapot
|
||||
418
|
||||
>>> requests.codes['\o/']
|
||||
200
|
||||
|
||||
Some codes have multiple names, and both upper- and lower-case versions of
|
||||
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
|
||||
``codes.okay`` all correspond to the HTTP status code 200.
|
||||
"""
|
||||
|
||||
from .structures import LookupDict
|
||||
|
||||
_codes = {
|
||||
@@ -84,8 +101,20 @@ _codes = {
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for code, titles in _codes.items():
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith(('\\', '/')):
|
||||
setattr(codes, title.upper(), code)
|
||||
def _init():
|
||||
for code, titles in _codes.items():
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith(('\\', '/')):
|
||||
setattr(codes, title.upper(), code)
|
||||
|
||||
def doc(code):
|
||||
names = ', '.join('``%s``' % n for n in _codes[code])
|
||||
return '* %d: %s' % (code, names)
|
||||
|
||||
global __doc__
|
||||
__doc__ = (__doc__ + '\n' +
|
||||
'\n'.join(doc(code) for code in sorted(_codes))
|
||||
if __doc__ is not None else None)
|
||||
|
||||
_init()
|
||||
|
||||
Vendored
+4
-6
@@ -7,16 +7,14 @@ requests.structures
|
||||
Data structures that power Requests.
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from .compat import OrderedDict
|
||||
from .compat import OrderedDict, Mapping, MutableMapping
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
class CaseInsensitiveDict(MutableMapping):
|
||||
"""A case-insensitive ``dict``-like object.
|
||||
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
``MutableMapping`` as well as dict's ``copy``. Also
|
||||
provides ``lower_items``.
|
||||
|
||||
All keys are expected to be strings. The structure remembers the
|
||||
@@ -71,7 +69,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, collections.Mapping):
|
||||
if isinstance(other, Mapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
Vendored
+94
-22
@@ -8,17 +8,17 @@ This module provides utility functions that are used within Requests
|
||||
that are also useful for external consumption.
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import collections
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
from .__version__ import __version__
|
||||
from . import certs
|
||||
@@ -28,7 +28,7 @@ from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import (
|
||||
quote, urlparse, bytes, str, OrderedDict, unquote, getproxies,
|
||||
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
|
||||
proxy_bypass_environment, getproxies_environment)
|
||||
proxy_bypass_environment, getproxies_environment, Mapping)
|
||||
from .cookies import cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .exceptions import (
|
||||
@@ -39,19 +39,25 @@ NETRC_FILES = ('.netrc', '_netrc')
|
||||
DEFAULT_CA_BUNDLE_PATH = certs.where()
|
||||
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
if sys.platform == 'win32':
|
||||
# provide a proxy_bypass version on Windows without DNS lookups
|
||||
|
||||
def proxy_bypass_registry(host):
|
||||
if is_py3:
|
||||
import winreg
|
||||
else:
|
||||
import _winreg as winreg
|
||||
try:
|
||||
if is_py3:
|
||||
import winreg
|
||||
else:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
try:
|
||||
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
|
||||
proxyEnable = winreg.QueryValueEx(internetSettings,
|
||||
'ProxyEnable')[0]
|
||||
# ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
|
||||
proxyEnable = int(winreg.QueryValueEx(internetSettings,
|
||||
'ProxyEnable')[0])
|
||||
# ProxyOverride is almost always a string
|
||||
proxyOverride = winreg.QueryValueEx(internetSettings,
|
||||
'ProxyOverride')[0]
|
||||
except OSError:
|
||||
@@ -216,6 +222,38 @@ def guess_filename(obj):
|
||||
return os.path.basename(name)
|
||||
|
||||
|
||||
def extract_zipped_paths(path):
|
||||
"""Replace nonexistent paths that look like they refer to a member of a zip
|
||||
archive with the location of an extracted copy of the target, or else
|
||||
just return the provided path unchanged.
|
||||
"""
|
||||
if os.path.exists(path):
|
||||
# this is already a valid path, no need to do anything further
|
||||
return path
|
||||
|
||||
# find the first valid part of the provided path and treat that as a zip archive
|
||||
# assume the rest of the path is the name of a member in the archive
|
||||
archive, member = os.path.split(path)
|
||||
while archive and not os.path.exists(archive):
|
||||
archive, prefix = os.path.split(archive)
|
||||
member = '/'.join([prefix, member])
|
||||
|
||||
if not zipfile.is_zipfile(archive):
|
||||
return path
|
||||
|
||||
zip_file = zipfile.ZipFile(archive)
|
||||
if member not in zip_file.namelist():
|
||||
return path
|
||||
|
||||
# we have a valid zip archive and a valid member of that archive
|
||||
tmp = tempfile.gettempdir()
|
||||
extracted_path = os.path.join(tmp, *member.split('/'))
|
||||
if not os.path.exists(extracted_path):
|
||||
extracted_path = zip_file.extract(member, path=tmp)
|
||||
|
||||
return extracted_path
|
||||
|
||||
|
||||
def from_key_val_list(value):
|
||||
"""Take an object and test to see if it can be represented as a
|
||||
dictionary. Unless it can not be represented as such, return an
|
||||
@@ -262,7 +300,7 @@ def to_key_val_list(value):
|
||||
if isinstance(value, (str, bytes, bool, int)):
|
||||
raise ValueError('cannot encode objects that are not 2-tuples')
|
||||
|
||||
if isinstance(value, collections.Mapping):
|
||||
if isinstance(value, Mapping):
|
||||
value = value.items()
|
||||
|
||||
return list(value)
|
||||
@@ -407,6 +445,31 @@ def get_encodings_from_content(content):
|
||||
xml_re.findall(content))
|
||||
|
||||
|
||||
def _parse_content_type_header(header):
|
||||
"""Returns content type and parameters from given header
|
||||
|
||||
:param header: string
|
||||
:return: tuple containing content type and dictionary of
|
||||
parameters
|
||||
"""
|
||||
|
||||
tokens = header.split(';')
|
||||
content_type, params = tokens[0].strip(), tokens[1:]
|
||||
params_dict = {}
|
||||
items_to_strip = "\"' "
|
||||
|
||||
for param in params:
|
||||
param = param.strip()
|
||||
if param:
|
||||
key, value = param, True
|
||||
index_of_equals = param.find("=")
|
||||
if index_of_equals != -1:
|
||||
key = param[:index_of_equals].strip(items_to_strip)
|
||||
value = param[index_of_equals + 1:].strip(items_to_strip)
|
||||
params_dict[key] = value
|
||||
return content_type, params_dict
|
||||
|
||||
|
||||
def get_encoding_from_headers(headers):
|
||||
"""Returns encodings from given HTTP Header Dict.
|
||||
|
||||
@@ -419,7 +482,7 @@ def get_encoding_from_headers(headers):
|
||||
if not content_type:
|
||||
return None
|
||||
|
||||
content_type, params = cgi.parse_header(content_type)
|
||||
content_type, params = _parse_content_type_header(content_type)
|
||||
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
@@ -632,6 +695,8 @@ def should_bypass_proxies(url, no_proxy):
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
# Prioritize lowercase environment variables over uppercase
|
||||
# to keep a consistent behaviour with other http projects (curl, wget).
|
||||
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
|
||||
|
||||
# First check whether no_proxy is defined. If it is, check that the URL
|
||||
@@ -639,28 +704,31 @@ def should_bypass_proxies(url, no_proxy):
|
||||
no_proxy_arg = no_proxy
|
||||
if no_proxy is None:
|
||||
no_proxy = get_proxy('no_proxy')
|
||||
netloc = urlparse(url).netloc
|
||||
parsed = urlparse(url)
|
||||
|
||||
if no_proxy:
|
||||
# We need to check whether we match here. We need to see if we match
|
||||
# the end of the netloc, both with and without the port.
|
||||
# the end of the hostname, both with and without the port.
|
||||
no_proxy = (
|
||||
host for host in no_proxy.replace(' ', '').split(',') if host
|
||||
)
|
||||
|
||||
ip = netloc.split(':')[0]
|
||||
if is_ipv4_address(ip):
|
||||
if is_ipv4_address(parsed.hostname):
|
||||
for proxy_ip in no_proxy:
|
||||
if is_valid_cidr(proxy_ip):
|
||||
if address_in_network(ip, proxy_ip):
|
||||
if address_in_network(parsed.hostname, proxy_ip):
|
||||
return True
|
||||
elif ip == proxy_ip:
|
||||
elif parsed.hostname == proxy_ip:
|
||||
# If no_proxy ip was defined in plain IP notation instead of cidr notation &
|
||||
# matches the IP of the index
|
||||
return True
|
||||
else:
|
||||
host_with_port = parsed.hostname
|
||||
if parsed.port:
|
||||
host_with_port += ':{0}'.format(parsed.port)
|
||||
|
||||
for host in no_proxy:
|
||||
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
|
||||
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
|
||||
# The URL does match something in no_proxy, so we don't want
|
||||
# to apply the proxies on this URL.
|
||||
return True
|
||||
@@ -673,7 +741,7 @@ def should_bypass_proxies(url, no_proxy):
|
||||
# legitimate problems.
|
||||
with set_environ('no_proxy', no_proxy_arg):
|
||||
try:
|
||||
bypass = proxy_bypass(netloc)
|
||||
bypass = proxy_bypass(parsed.hostname)
|
||||
except (TypeError, socket.gaierror):
|
||||
bypass = False
|
||||
|
||||
@@ -743,7 +811,7 @@ def default_headers():
|
||||
|
||||
|
||||
def parse_header_links(value):
|
||||
"""Return a dict of parsed link headers proxies.
|
||||
"""Return a list of parsed link headers proxies.
|
||||
|
||||
i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"
|
||||
|
||||
@@ -754,6 +822,10 @@ def parse_header_links(value):
|
||||
|
||||
replace_chars = ' \'"'
|
||||
|
||||
value = value.strip(replace_chars)
|
||||
if not value:
|
||||
return links
|
||||
|
||||
for val in re.split(', *<', value):
|
||||
try:
|
||||
url, params = val.split(';', 1)
|
||||
|
||||
+3
-21
@@ -1,21 +1,3 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2018 Dan Ryan.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
This software is made available under the terms of *either* of the licenses
|
||||
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
||||
under the terms of *both* these licenses.
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
Copyright (c) Kenneth Reitz and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE 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 HOLDER 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile
|
||||
all: clean vendor
|
||||
|
||||
clean:
|
||||
@# Delete vendored items
|
||||
find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \;
|
||||
|
||||
vendor:
|
||||
@# Install vendored libraries
|
||||
pip install -t . -r vendor.txt
|
||||
|
||||
@# Cleanup .egg-info directories
|
||||
rm -rf *.egg-info
|
||||
rm -rf *.dist-info
|
||||
@@ -1 +0,0 @@
|
||||
pipfile
|
||||
+12
-7
@@ -187,7 +187,7 @@ class FileRequirement(BaseRequirement):
|
||||
if parsed_url.scheme == "file" and parsed_url.path:
|
||||
# This is a "file://" URI. Use url_to_path and path_to_url to
|
||||
# ensure the path is absolute. Also we need to build relpath.
|
||||
path = Path(url_to_path(unquote(urllib_parse.urlunsplit(parsed_url)))).as_posix()
|
||||
path = Path(url_to_path(urllib_parse.urlunsplit(parsed_url))).as_posix()
|
||||
try:
|
||||
relpath = get_converted_relative_path(path)
|
||||
except ValueError:
|
||||
@@ -198,17 +198,17 @@ class FileRequirement(BaseRequirement):
|
||||
path = None
|
||||
relpath = None
|
||||
# Cut the fragment, but otherwise this is fixed_line.
|
||||
uri = unquote(urllib_parse.urlunsplit(
|
||||
uri = urllib_parse.urlunsplit(
|
||||
parsed_url._replace(scheme=original_scheme, fragment="")
|
||||
))
|
||||
)
|
||||
|
||||
if added_ssh_scheme:
|
||||
uri = strip_ssh_from_git_uri(uri)
|
||||
|
||||
# Re-attach VCS prefix to build a Link.
|
||||
link = Link(unquote(
|
||||
link = Link(
|
||||
urllib_parse.urlunsplit(parsed_url._replace(scheme=original_scheme))
|
||||
))
|
||||
)
|
||||
|
||||
return LinkInfo(vcs_type, prefer, relpath, path, uri, link)
|
||||
|
||||
@@ -228,7 +228,12 @@ class FileRequirement(BaseRequirement):
|
||||
return self.link.egg_fragment
|
||||
elif self.link and self.link.is_wheel:
|
||||
return Wheel(self.link.filename).name
|
||||
if self._uri_scheme != "uri" and self.path and self.setup_path and self.setup_path.exists():
|
||||
if (
|
||||
self._uri_scheme != "uri"
|
||||
and self.path
|
||||
and self.setup_path
|
||||
and self.setup_path.exists()
|
||||
):
|
||||
from distutils.core import run_setup
|
||||
|
||||
try:
|
||||
@@ -357,7 +362,7 @@ class FileRequirement(BaseRequirement):
|
||||
else:
|
||||
# URI is not currently a valid key in pipfile entries
|
||||
# see https://github.com/pypa/pipfile/issues/110
|
||||
uri_scheme = 'file'
|
||||
uri_scheme = "file"
|
||||
|
||||
if not uri:
|
||||
uri = path_to_url(path)
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ def strip_ssh_from_git_uri(uri):
|
||||
|
||||
|
||||
def add_ssh_scheme_to_git_uri(uri):
|
||||
"""Cleans VCS uris from pip format"""
|
||||
"""Cleans VCS uris from pipenv.patched.notpip format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
# Add scheme for parsing purposes, this is also what pip does
|
||||
if uri.startswith("git+") and "://" not in uri:
|
||||
|
||||
-3
@@ -34,9 +34,6 @@ log = setup_logger()
|
||||
|
||||
|
||||
def is_vcs(pipfile_entry):
|
||||
import requirements
|
||||
from .models.utils import add_ssh_scheme_to_git_uri
|
||||
|
||||
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
|
||||
if hasattr(pipfile_entry, "keys"):
|
||||
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
|
||||
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012, Ben Hoyt
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, 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.
|
||||
|
||||
* Neither the name of Ben Hoyt nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE 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 HOLDER 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+680
@@ -0,0 +1,680 @@
|
||||
"""scandir, a better directory iterator and faster os.walk(), now in the Python 3.5 stdlib
|
||||
|
||||
scandir() is a generator version of os.listdir() that returns an
|
||||
iterator over files in a directory, and also exposes the extra
|
||||
information most OSes provide while iterating files in a directory
|
||||
(such as type and stat information).
|
||||
|
||||
This module also includes a version of os.walk() that uses scandir()
|
||||
to speed it up significantly.
|
||||
|
||||
See README.md or https://github.com/benhoyt/scandir for rationale and
|
||||
docs, or read PEP 471 (https://www.python.org/dev/peps/pep-0471/) for
|
||||
more details on its inclusion into Python 3.5
|
||||
|
||||
scandir is released under the new BSD 3-clause license. See
|
||||
LICENSE.txt for the full license text.
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
from errno import ENOENT
|
||||
from os import listdir, lstat, stat, strerror
|
||||
from os.path import join, islink
|
||||
from stat import S_IFDIR, S_IFLNK, S_IFREG
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import _scandir
|
||||
except ImportError:
|
||||
_scandir = None
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
except ImportError:
|
||||
ctypes = None
|
||||
|
||||
if _scandir is None and ctypes is None:
|
||||
import warnings
|
||||
warnings.warn("scandir can't find the compiled _scandir C module "
|
||||
"or ctypes, using slow generic fallback")
|
||||
|
||||
__version__ = '1.7'
|
||||
__all__ = ['scandir', 'walk']
|
||||
|
||||
# Windows FILE_ATTRIBUTE constants for interpreting the
|
||||
# FIND_DATA.dwFileAttributes member
|
||||
FILE_ATTRIBUTE_ARCHIVE = 32
|
||||
FILE_ATTRIBUTE_COMPRESSED = 2048
|
||||
FILE_ATTRIBUTE_DEVICE = 64
|
||||
FILE_ATTRIBUTE_DIRECTORY = 16
|
||||
FILE_ATTRIBUTE_ENCRYPTED = 16384
|
||||
FILE_ATTRIBUTE_HIDDEN = 2
|
||||
FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768
|
||||
FILE_ATTRIBUTE_NORMAL = 128
|
||||
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192
|
||||
FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072
|
||||
FILE_ATTRIBUTE_OFFLINE = 4096
|
||||
FILE_ATTRIBUTE_READONLY = 1
|
||||
FILE_ATTRIBUTE_REPARSE_POINT = 1024
|
||||
FILE_ATTRIBUTE_SPARSE_FILE = 512
|
||||
FILE_ATTRIBUTE_SYSTEM = 4
|
||||
FILE_ATTRIBUTE_TEMPORARY = 256
|
||||
FILE_ATTRIBUTE_VIRTUAL = 65536
|
||||
|
||||
IS_PY3 = sys.version_info >= (3, 0)
|
||||
|
||||
if IS_PY3:
|
||||
unicode = str # Because Python <= 3.2 doesn't have u'unicode' syntax
|
||||
|
||||
|
||||
class GenericDirEntry(object):
|
||||
__slots__ = ('name', '_stat', '_lstat', '_scandir_path', '_path')
|
||||
|
||||
def __init__(self, scandir_path, name):
|
||||
self._scandir_path = scandir_path
|
||||
self.name = name
|
||||
self._stat = None
|
||||
self._lstat = None
|
||||
self._path = None
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
if self._path is None:
|
||||
self._path = join(self._scandir_path, self.name)
|
||||
return self._path
|
||||
|
||||
def stat(self, follow_symlinks=True):
|
||||
if follow_symlinks:
|
||||
if self._stat is None:
|
||||
self._stat = stat(self.path)
|
||||
return self._stat
|
||||
else:
|
||||
if self._lstat is None:
|
||||
self._lstat = lstat(self.path)
|
||||
return self._lstat
|
||||
|
||||
def is_dir(self, follow_symlinks=True):
|
||||
try:
|
||||
st = self.stat(follow_symlinks=follow_symlinks)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False # Path doesn't exist or is a broken symlink
|
||||
return st.st_mode & 0o170000 == S_IFDIR
|
||||
|
||||
def is_file(self, follow_symlinks=True):
|
||||
try:
|
||||
st = self.stat(follow_symlinks=follow_symlinks)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False # Path doesn't exist or is a broken symlink
|
||||
return st.st_mode & 0o170000 == S_IFREG
|
||||
|
||||
def is_symlink(self):
|
||||
try:
|
||||
st = self.stat(follow_symlinks=False)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False # Path doesn't exist or is a broken symlink
|
||||
return st.st_mode & 0o170000 == S_IFLNK
|
||||
|
||||
def inode(self):
|
||||
st = self.stat(follow_symlinks=False)
|
||||
return st.st_ino
|
||||
|
||||
def __str__(self):
|
||||
return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
def _scandir_generic(path=unicode('.')):
|
||||
"""Like os.listdir(), but yield DirEntry objects instead of returning
|
||||
a list of names.
|
||||
"""
|
||||
for name in listdir(path):
|
||||
yield GenericDirEntry(path, name)
|
||||
|
||||
|
||||
if IS_PY3 and sys.platform == 'win32':
|
||||
def scandir_generic(path=unicode('.')):
|
||||
if isinstance(path, bytes):
|
||||
raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
|
||||
return _scandir_generic(path)
|
||||
scandir_generic.__doc__ = _scandir_generic.__doc__
|
||||
else:
|
||||
scandir_generic = _scandir_generic
|
||||
|
||||
|
||||
scandir_c = None
|
||||
scandir_python = None
|
||||
|
||||
|
||||
if sys.platform == 'win32':
|
||||
if ctypes is not None:
|
||||
from ctypes import wintypes
|
||||
|
||||
# Various constants from windows.h
|
||||
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
|
||||
ERROR_FILE_NOT_FOUND = 2
|
||||
ERROR_NO_MORE_FILES = 18
|
||||
IO_REPARSE_TAG_SYMLINK = 0xA000000C
|
||||
|
||||
# Numer of seconds between 1601-01-01 and 1970-01-01
|
||||
SECONDS_BETWEEN_EPOCHS = 11644473600
|
||||
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
|
||||
# ctypes wrappers for (wide string versions of) FindFirstFile,
|
||||
# FindNextFile, and FindClose
|
||||
FindFirstFile = kernel32.FindFirstFileW
|
||||
FindFirstFile.argtypes = [
|
||||
wintypes.LPCWSTR,
|
||||
ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
|
||||
]
|
||||
FindFirstFile.restype = wintypes.HANDLE
|
||||
|
||||
FindNextFile = kernel32.FindNextFileW
|
||||
FindNextFile.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
ctypes.POINTER(wintypes.WIN32_FIND_DATAW),
|
||||
]
|
||||
FindNextFile.restype = wintypes.BOOL
|
||||
|
||||
FindClose = kernel32.FindClose
|
||||
FindClose.argtypes = [wintypes.HANDLE]
|
||||
FindClose.restype = wintypes.BOOL
|
||||
|
||||
Win32StatResult = collections.namedtuple('Win32StatResult', [
|
||||
'st_mode',
|
||||
'st_ino',
|
||||
'st_dev',
|
||||
'st_nlink',
|
||||
'st_uid',
|
||||
'st_gid',
|
||||
'st_size',
|
||||
'st_atime',
|
||||
'st_mtime',
|
||||
'st_ctime',
|
||||
'st_atime_ns',
|
||||
'st_mtime_ns',
|
||||
'st_ctime_ns',
|
||||
'st_file_attributes',
|
||||
])
|
||||
|
||||
def filetime_to_time(filetime):
|
||||
"""Convert Win32 FILETIME to time since Unix epoch in seconds."""
|
||||
total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime
|
||||
return total / 10000000 - SECONDS_BETWEEN_EPOCHS
|
||||
|
||||
def find_data_to_stat(data):
|
||||
"""Convert Win32 FIND_DATA struct to stat_result."""
|
||||
# First convert Win32 dwFileAttributes to st_mode
|
||||
attributes = data.dwFileAttributes
|
||||
st_mode = 0
|
||||
if attributes & FILE_ATTRIBUTE_DIRECTORY:
|
||||
st_mode |= S_IFDIR | 0o111
|
||||
else:
|
||||
st_mode |= S_IFREG
|
||||
if attributes & FILE_ATTRIBUTE_READONLY:
|
||||
st_mode |= 0o444
|
||||
else:
|
||||
st_mode |= 0o666
|
||||
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT and
|
||||
data.dwReserved0 == IO_REPARSE_TAG_SYMLINK):
|
||||
st_mode ^= st_mode & 0o170000
|
||||
st_mode |= S_IFLNK
|
||||
|
||||
st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow
|
||||
st_atime = filetime_to_time(data.ftLastAccessTime)
|
||||
st_mtime = filetime_to_time(data.ftLastWriteTime)
|
||||
st_ctime = filetime_to_time(data.ftCreationTime)
|
||||
|
||||
# Some fields set to zero per CPython's posixmodule.c: st_ino, st_dev,
|
||||
# st_nlink, st_uid, st_gid
|
||||
return Win32StatResult(st_mode, 0, 0, 0, 0, 0, st_size,
|
||||
st_atime, st_mtime, st_ctime,
|
||||
int(st_atime * 1000000000),
|
||||
int(st_mtime * 1000000000),
|
||||
int(st_ctime * 1000000000),
|
||||
attributes)
|
||||
|
||||
class Win32DirEntryPython(object):
|
||||
__slots__ = ('name', '_stat', '_lstat', '_find_data', '_scandir_path', '_path', '_inode')
|
||||
|
||||
def __init__(self, scandir_path, name, find_data):
|
||||
self._scandir_path = scandir_path
|
||||
self.name = name
|
||||
self._stat = None
|
||||
self._lstat = None
|
||||
self._find_data = find_data
|
||||
self._path = None
|
||||
self._inode = None
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
if self._path is None:
|
||||
self._path = join(self._scandir_path, self.name)
|
||||
return self._path
|
||||
|
||||
def stat(self, follow_symlinks=True):
|
||||
if follow_symlinks:
|
||||
if self._stat is None:
|
||||
if self.is_symlink():
|
||||
# It's a symlink, call link-following stat()
|
||||
self._stat = stat(self.path)
|
||||
else:
|
||||
# Not a symlink, stat is same as lstat value
|
||||
if self._lstat is None:
|
||||
self._lstat = find_data_to_stat(self._find_data)
|
||||
self._stat = self._lstat
|
||||
return self._stat
|
||||
else:
|
||||
if self._lstat is None:
|
||||
# Lazily convert to stat object, because it's slow
|
||||
# in Python, and often we only need is_dir() etc
|
||||
self._lstat = find_data_to_stat(self._find_data)
|
||||
return self._lstat
|
||||
|
||||
def is_dir(self, follow_symlinks=True):
|
||||
is_symlink = self.is_symlink()
|
||||
if follow_symlinks and is_symlink:
|
||||
try:
|
||||
return self.stat().st_mode & 0o170000 == S_IFDIR
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False
|
||||
elif is_symlink:
|
||||
return False
|
||||
else:
|
||||
return (self._find_data.dwFileAttributes &
|
||||
FILE_ATTRIBUTE_DIRECTORY != 0)
|
||||
|
||||
def is_file(self, follow_symlinks=True):
|
||||
is_symlink = self.is_symlink()
|
||||
if follow_symlinks and is_symlink:
|
||||
try:
|
||||
return self.stat().st_mode & 0o170000 == S_IFREG
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False
|
||||
elif is_symlink:
|
||||
return False
|
||||
else:
|
||||
return (self._find_data.dwFileAttributes &
|
||||
FILE_ATTRIBUTE_DIRECTORY == 0)
|
||||
|
||||
def is_symlink(self):
|
||||
return (self._find_data.dwFileAttributes &
|
||||
FILE_ATTRIBUTE_REPARSE_POINT != 0 and
|
||||
self._find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
|
||||
|
||||
def inode(self):
|
||||
if self._inode is None:
|
||||
self._inode = lstat(self.path).st_ino
|
||||
return self._inode
|
||||
|
||||
def __str__(self):
|
||||
return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def win_error(error, filename):
|
||||
exc = WindowsError(error, ctypes.FormatError(error))
|
||||
exc.filename = filename
|
||||
return exc
|
||||
|
||||
def _scandir_python(path=unicode('.')):
|
||||
"""Like os.listdir(), but yield DirEntry objects instead of returning
|
||||
a list of names.
|
||||
"""
|
||||
# Call FindFirstFile and handle errors
|
||||
if isinstance(path, bytes):
|
||||
is_bytes = True
|
||||
filename = join(path.decode('mbcs', 'strict'), '*.*')
|
||||
else:
|
||||
is_bytes = False
|
||||
filename = join(path, '*.*')
|
||||
data = wintypes.WIN32_FIND_DATAW()
|
||||
data_p = ctypes.byref(data)
|
||||
handle = FindFirstFile(filename, data_p)
|
||||
if handle == INVALID_HANDLE_VALUE:
|
||||
error = ctypes.GetLastError()
|
||||
if error == ERROR_FILE_NOT_FOUND:
|
||||
# No files, don't yield anything
|
||||
return
|
||||
raise win_error(error, path)
|
||||
|
||||
# Call FindNextFile in a loop, stopping when no more files
|
||||
try:
|
||||
while True:
|
||||
# Skip '.' and '..' (current and parent directory), but
|
||||
# otherwise yield (filename, stat_result) tuple
|
||||
name = data.cFileName
|
||||
if name not in ('.', '..'):
|
||||
if is_bytes:
|
||||
name = name.encode('mbcs', 'replace')
|
||||
yield Win32DirEntryPython(path, name, data)
|
||||
|
||||
data = wintypes.WIN32_FIND_DATAW()
|
||||
data_p = ctypes.byref(data)
|
||||
success = FindNextFile(handle, data_p)
|
||||
if not success:
|
||||
error = ctypes.GetLastError()
|
||||
if error == ERROR_NO_MORE_FILES:
|
||||
break
|
||||
raise win_error(error, path)
|
||||
finally:
|
||||
if not FindClose(handle):
|
||||
raise win_error(ctypes.GetLastError(), path)
|
||||
|
||||
if IS_PY3:
|
||||
def scandir_python(path=unicode('.')):
|
||||
if isinstance(path, bytes):
|
||||
raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead")
|
||||
return _scandir_python(path)
|
||||
scandir_python.__doc__ = _scandir_python.__doc__
|
||||
else:
|
||||
scandir_python = _scandir_python
|
||||
|
||||
if _scandir is not None:
|
||||
scandir_c = _scandir.scandir
|
||||
DirEntry_c = _scandir.DirEntry
|
||||
|
||||
if _scandir is not None:
|
||||
scandir = scandir_c
|
||||
DirEntry = DirEntry_c
|
||||
elif ctypes is not None:
|
||||
scandir = scandir_python
|
||||
DirEntry = Win32DirEntryPython
|
||||
else:
|
||||
scandir = scandir_generic
|
||||
DirEntry = GenericDirEntry
|
||||
|
||||
|
||||
# Linux, OS X, and BSD implementation
|
||||
elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform:
|
||||
have_dirent_d_type = (sys.platform != 'sunos5')
|
||||
|
||||
if ctypes is not None and have_dirent_d_type:
|
||||
import ctypes.util
|
||||
|
||||
DIR_p = ctypes.c_void_p
|
||||
|
||||
# Rather annoying how the dirent struct is slightly different on each
|
||||
# platform. The only fields we care about are d_name and d_type.
|
||||
class Dirent(ctypes.Structure):
|
||||
if sys.platform.startswith('linux'):
|
||||
_fields_ = (
|
||||
('d_ino', ctypes.c_ulong),
|
||||
('d_off', ctypes.c_long),
|
||||
('d_reclen', ctypes.c_ushort),
|
||||
('d_type', ctypes.c_byte),
|
||||
('d_name', ctypes.c_char * 256),
|
||||
)
|
||||
else:
|
||||
_fields_ = (
|
||||
('d_ino', ctypes.c_uint32), # must be uint32, not ulong
|
||||
('d_reclen', ctypes.c_ushort),
|
||||
('d_type', ctypes.c_byte),
|
||||
('d_namlen', ctypes.c_byte),
|
||||
('d_name', ctypes.c_char * 256),
|
||||
)
|
||||
|
||||
DT_UNKNOWN = 0
|
||||
DT_DIR = 4
|
||||
DT_REG = 8
|
||||
DT_LNK = 10
|
||||
|
||||
Dirent_p = ctypes.POINTER(Dirent)
|
||||
Dirent_pp = ctypes.POINTER(Dirent_p)
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
|
||||
opendir = libc.opendir
|
||||
opendir.argtypes = [ctypes.c_char_p]
|
||||
opendir.restype = DIR_p
|
||||
|
||||
readdir_r = libc.readdir_r
|
||||
readdir_r.argtypes = [DIR_p, Dirent_p, Dirent_pp]
|
||||
readdir_r.restype = ctypes.c_int
|
||||
|
||||
closedir = libc.closedir
|
||||
closedir.argtypes = [DIR_p]
|
||||
closedir.restype = ctypes.c_int
|
||||
|
||||
file_system_encoding = sys.getfilesystemencoding()
|
||||
|
||||
class PosixDirEntry(object):
|
||||
__slots__ = ('name', '_d_type', '_stat', '_lstat', '_scandir_path', '_path', '_inode')
|
||||
|
||||
def __init__(self, scandir_path, name, d_type, inode):
|
||||
self._scandir_path = scandir_path
|
||||
self.name = name
|
||||
self._d_type = d_type
|
||||
self._inode = inode
|
||||
self._stat = None
|
||||
self._lstat = None
|
||||
self._path = None
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
if self._path is None:
|
||||
self._path = join(self._scandir_path, self.name)
|
||||
return self._path
|
||||
|
||||
def stat(self, follow_symlinks=True):
|
||||
if follow_symlinks:
|
||||
if self._stat is None:
|
||||
if self.is_symlink():
|
||||
self._stat = stat(self.path)
|
||||
else:
|
||||
if self._lstat is None:
|
||||
self._lstat = lstat(self.path)
|
||||
self._stat = self._lstat
|
||||
return self._stat
|
||||
else:
|
||||
if self._lstat is None:
|
||||
self._lstat = lstat(self.path)
|
||||
return self._lstat
|
||||
|
||||
def is_dir(self, follow_symlinks=True):
|
||||
if (self._d_type == DT_UNKNOWN or
|
||||
(follow_symlinks and self.is_symlink())):
|
||||
try:
|
||||
st = self.stat(follow_symlinks=follow_symlinks)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False
|
||||
return st.st_mode & 0o170000 == S_IFDIR
|
||||
else:
|
||||
return self._d_type == DT_DIR
|
||||
|
||||
def is_file(self, follow_symlinks=True):
|
||||
if (self._d_type == DT_UNKNOWN or
|
||||
(follow_symlinks and self.is_symlink())):
|
||||
try:
|
||||
st = self.stat(follow_symlinks=follow_symlinks)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False
|
||||
return st.st_mode & 0o170000 == S_IFREG
|
||||
else:
|
||||
return self._d_type == DT_REG
|
||||
|
||||
def is_symlink(self):
|
||||
if self._d_type == DT_UNKNOWN:
|
||||
try:
|
||||
st = self.stat(follow_symlinks=False)
|
||||
except OSError as e:
|
||||
if e.errno != ENOENT:
|
||||
raise
|
||||
return False
|
||||
return st.st_mode & 0o170000 == S_IFLNK
|
||||
else:
|
||||
return self._d_type == DT_LNK
|
||||
|
||||
def inode(self):
|
||||
return self._inode
|
||||
|
||||
def __str__(self):
|
||||
return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def posix_error(filename):
|
||||
errno = ctypes.get_errno()
|
||||
exc = OSError(errno, strerror(errno))
|
||||
exc.filename = filename
|
||||
return exc
|
||||
|
||||
def scandir_python(path=unicode('.')):
|
||||
"""Like os.listdir(), but yield DirEntry objects instead of returning
|
||||
a list of names.
|
||||
"""
|
||||
if isinstance(path, bytes):
|
||||
opendir_path = path
|
||||
is_bytes = True
|
||||
else:
|
||||
opendir_path = path.encode(file_system_encoding)
|
||||
is_bytes = False
|
||||
dir_p = opendir(opendir_path)
|
||||
if not dir_p:
|
||||
raise posix_error(path)
|
||||
try:
|
||||
result = Dirent_p()
|
||||
while True:
|
||||
entry = Dirent()
|
||||
if readdir_r(dir_p, entry, result):
|
||||
raise posix_error(path)
|
||||
if not result:
|
||||
break
|
||||
name = entry.d_name
|
||||
if name not in (b'.', b'..'):
|
||||
if not is_bytes:
|
||||
name = name.decode(file_system_encoding)
|
||||
yield PosixDirEntry(path, name, entry.d_type, entry.d_ino)
|
||||
finally:
|
||||
if closedir(dir_p):
|
||||
raise posix_error(path)
|
||||
|
||||
if _scandir is not None:
|
||||
scandir_c = _scandir.scandir
|
||||
DirEntry_c = _scandir.DirEntry
|
||||
|
||||
if _scandir is not None:
|
||||
scandir = scandir_c
|
||||
DirEntry = DirEntry_c
|
||||
elif ctypes is not None:
|
||||
scandir = scandir_python
|
||||
DirEntry = PosixDirEntry
|
||||
else:
|
||||
scandir = scandir_generic
|
||||
DirEntry = GenericDirEntry
|
||||
|
||||
|
||||
# Some other system -- no d_type or stat information
|
||||
else:
|
||||
scandir = scandir_generic
|
||||
DirEntry = GenericDirEntry
|
||||
|
||||
|
||||
def _walk(top, topdown=True, onerror=None, followlinks=False):
|
||||
"""Like Python 3.5's implementation of os.walk() -- faster than
|
||||
the pre-Python 3.5 version as it uses scandir() internally.
|
||||
"""
|
||||
dirs = []
|
||||
nondirs = []
|
||||
|
||||
# We may not have read permission for top, in which case we can't
|
||||
# get a list of the files the directory contains. os.walk
|
||||
# always suppressed the exception then, rather than blow up for a
|
||||
# minor reason when (say) a thousand readable directories are still
|
||||
# left to visit. That logic is copied here.
|
||||
try:
|
||||
scandir_it = scandir(top)
|
||||
except OSError as error:
|
||||
if onerror is not None:
|
||||
onerror(error)
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
entry = next(scandir_it)
|
||||
except StopIteration:
|
||||
break
|
||||
except OSError as error:
|
||||
if onerror is not None:
|
||||
onerror(error)
|
||||
return
|
||||
|
||||
try:
|
||||
is_dir = entry.is_dir()
|
||||
except OSError:
|
||||
# If is_dir() raises an OSError, consider that the entry is not
|
||||
# a directory, same behaviour than os.path.isdir().
|
||||
is_dir = False
|
||||
|
||||
if is_dir:
|
||||
dirs.append(entry.name)
|
||||
else:
|
||||
nondirs.append(entry.name)
|
||||
|
||||
if not topdown and is_dir:
|
||||
# Bottom-up: recurse into sub-directory, but exclude symlinks to
|
||||
# directories if followlinks is False
|
||||
if followlinks:
|
||||
walk_into = True
|
||||
else:
|
||||
try:
|
||||
is_symlink = entry.is_symlink()
|
||||
except OSError:
|
||||
# If is_symlink() raises an OSError, consider that the
|
||||
# entry is not a symbolic link, same behaviour than
|
||||
# os.path.islink().
|
||||
is_symlink = False
|
||||
walk_into = not is_symlink
|
||||
|
||||
if walk_into:
|
||||
for entry in walk(entry.path, topdown, onerror, followlinks):
|
||||
yield entry
|
||||
|
||||
# Yield before recursion if going top down
|
||||
if topdown:
|
||||
yield top, dirs, nondirs
|
||||
|
||||
# Recurse into sub-directories
|
||||
for name in dirs:
|
||||
new_path = join(top, name)
|
||||
# Issue #23605: os.path.islink() is used instead of caching
|
||||
# entry.is_symlink() result during the loop on os.scandir() because
|
||||
# the caller can replace the directory entry during the "yield"
|
||||
# above.
|
||||
if followlinks or not islink(new_path):
|
||||
for entry in walk(new_path, topdown, onerror, followlinks):
|
||||
yield entry
|
||||
else:
|
||||
# Yield after recursion if going bottom up
|
||||
yield top, dirs, nondirs
|
||||
|
||||
|
||||
if IS_PY3 or sys.platform != 'win32':
|
||||
walk = _walk
|
||||
else:
|
||||
# Fix for broken unicode handling on Windows on Python 2.x, see:
|
||||
# https://github.com/benhoyt/scandir/issues/54
|
||||
file_system_encoding = sys.getfilesystemencoding()
|
||||
|
||||
def walk(top, topdown=True, onerror=None, followlinks=False):
|
||||
if isinstance(top, bytes):
|
||||
top = top.decode(file_system_encoding)
|
||||
return _walk(top, topdown, onerror, followlinks)
|
||||
Vendored
+20
-1
@@ -6,9 +6,11 @@ import collections
|
||||
import re
|
||||
|
||||
|
||||
__version__ = '2.7.8'
|
||||
__version__ = '2.8.0'
|
||||
__author__ = 'Kostiantyn Rybnikov'
|
||||
__author_email__ = 'k-bx@k-bx.com'
|
||||
__maintainer__ = 'Sebastien Celles'
|
||||
__maintainer_email__ = "s.celles@gmail.com"
|
||||
|
||||
_REGEX = re.compile(
|
||||
r"""
|
||||
@@ -106,6 +108,12 @@ class VersionInfo(collections.namedtuple(
|
||||
return NotImplemented
|
||||
return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0
|
||||
|
||||
def __str__(self):
|
||||
return format_version(*self)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(tuple(self))
|
||||
|
||||
|
||||
def _to_dict(obj):
|
||||
if isinstance(obj, VersionInfo):
|
||||
@@ -358,3 +366,14 @@ def bump_build(version, token='build'):
|
||||
)
|
||||
return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'],
|
||||
verinfo['prerelease'], verinfo['build'])
|
||||
|
||||
|
||||
def finalize_version(version):
|
||||
"""Remove any prerelease and build metadata from the version
|
||||
|
||||
:param version: version string
|
||||
:return: the finalized version string
|
||||
:rtype: str
|
||||
"""
|
||||
verinfo = parse(version)
|
||||
return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'])
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2010-2015 Benjamin Peterson
|
||||
Copyright (c) 2010-2017 Benjamin Peterson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
Vendored
+39
-16
@@ -1,6 +1,4 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
# Copyright (c) 2010-2017 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,6 +18,8 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
@@ -29,7 +29,7 @@ import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.10.0"
|
||||
__version__ = "1.11.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
@@ -241,6 +241,7 @@ _moved_attributes = [
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
||||
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
||||
MovedAttribute("getoutput", "commands", "subprocess"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
@@ -262,10 +263,11 @@ _moved_attributes = [
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
@@ -337,10 +339,12 @@ _urllib_parse_moved_attributes = [
|
||||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
|
||||
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
||||
@@ -416,6 +420,8 @@ _urllib_request_moved_attributes = [
|
||||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
|
||||
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
@@ -679,11 +685,15 @@ if PY3:
|
||||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
try:
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
finally:
|
||||
value = None
|
||||
tb = None
|
||||
|
||||
else:
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
@@ -699,19 +709,28 @@ else:
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
try:
|
||||
raise tp, value, tb
|
||||
finally:
|
||||
tb = None
|
||||
""")
|
||||
|
||||
|
||||
if sys.version_info[:2] == (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
try:
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
finally:
|
||||
value = None
|
||||
""")
|
||||
elif sys.version_info[:2] > (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
raise value from from_value
|
||||
try:
|
||||
raise value from from_value
|
||||
finally:
|
||||
value = None
|
||||
""")
|
||||
else:
|
||||
def raise_from(value, from_value):
|
||||
@@ -802,10 +821,14 @@ def with_metaclass(meta, *bases):
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
class metaclass(type):
|
||||
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
|
||||
@classmethod
|
||||
def __prepare__(cls, name, this_bases):
|
||||
return meta.__prepare__(name, bases)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ except ImportError:
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = '1.22'
|
||||
__version__ = '1.23'
|
||||
|
||||
__all__ = (
|
||||
'HTTPConnectionPool',
|
||||
|
||||
+18
-5
@@ -1,5 +1,8 @@
|
||||
from __future__ import absolute_import
|
||||
from collections import Mapping, MutableMapping
|
||||
try:
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
except ImportError:
|
||||
from collections import Mapping, MutableMapping
|
||||
try:
|
||||
from threading import RLock
|
||||
except ImportError: # Platform-specific: No threads available
|
||||
@@ -15,6 +18,7 @@ try: # Python 2.7+
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from .packages.ordered_dict import OrderedDict
|
||||
from .exceptions import InvalidHeader
|
||||
from .packages.six import iterkeys, itervalues, PY3
|
||||
|
||||
|
||||
@@ -305,13 +309,22 @@ class HTTPHeaderDict(MutableMapping):
|
||||
# python2.7 does not expose a proper API for exporting multiheaders
|
||||
# efficiently. This function re-reads raw lines from the message
|
||||
# object and extracts the multiheaders properly.
|
||||
obs_fold_continued_leaders = (' ', '\t')
|
||||
headers = []
|
||||
|
||||
for line in message.headers:
|
||||
if line.startswith((' ', '\t')):
|
||||
key, value = headers[-1]
|
||||
headers[-1] = (key, value + '\r\n' + line.rstrip())
|
||||
continue
|
||||
if line.startswith(obs_fold_continued_leaders):
|
||||
if not headers:
|
||||
# We received a header line that starts with OWS as described
|
||||
# in RFC-7230 S3.2.4. This indicates a multiline header, but
|
||||
# there exists no previous header to which we can attach it.
|
||||
raise InvalidHeader(
|
||||
'Header continuation with no previous header: %s' % line
|
||||
)
|
||||
else:
|
||||
key, value = headers[-1]
|
||||
headers[-1] = (key, value + ' ' + line.strip())
|
||||
continue
|
||||
|
||||
key, value = line.split(':', 1)
|
||||
headers.append((key, value.strip()))
|
||||
|
||||
+35
-5
@@ -56,10 +56,11 @@ port_by_scheme = {
|
||||
'https': 443,
|
||||
}
|
||||
|
||||
# When updating RECENT_DATE, move it to
|
||||
# within two years of the current date, and no
|
||||
# earlier than 6 months ago.
|
||||
RECENT_DATE = datetime.date(2016, 1, 1)
|
||||
# When updating RECENT_DATE, move it to within two years of the current date,
|
||||
# and not less than 6 months ago.
|
||||
# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or
|
||||
# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months)
|
||||
RECENT_DATE = datetime.date(2017, 6, 30)
|
||||
|
||||
|
||||
class DummyConnection(object):
|
||||
@@ -124,6 +125,35 @@ class HTTPConnection(_HTTPConnection, object):
|
||||
# Superclass also sets self.source_address in Python 2.7+.
|
||||
_HTTPConnection.__init__(self, *args, **kw)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
"""
|
||||
Getter method to remove any trailing dots that indicate the hostname is an FQDN.
|
||||
|
||||
In general, SSL certificates don't include the trailing dot indicating a
|
||||
fully-qualified domain name, and thus, they don't validate properly when
|
||||
checked against a domain name that includes the dot. In addition, some
|
||||
servers may not expect to receive the trailing dot when provided.
|
||||
|
||||
However, the hostname with trailing dot is critical to DNS resolution; doing a
|
||||
lookup with the trailing dot will properly only resolve the appropriate FQDN,
|
||||
whereas a lookup without a trailing dot will search the system's search domain
|
||||
list. Thus, it's important to keep the original host around for use only in
|
||||
those cases where it's appropriate (i.e., when doing DNS lookup to establish the
|
||||
actual TCP connection across which we're going to send HTTP requests).
|
||||
"""
|
||||
return self._dns_host.rstrip('.')
|
||||
|
||||
@host.setter
|
||||
def host(self, value):
|
||||
"""
|
||||
Setter for the `host` property.
|
||||
|
||||
We assume that only urllib3 uses the _dns_host attribute; httplib itself
|
||||
only uses `host`, and it seems reasonable that other libraries follow suit.
|
||||
"""
|
||||
self._dns_host = value
|
||||
|
||||
def _new_conn(self):
|
||||
""" Establish a socket connection and set nodelay settings on it.
|
||||
|
||||
@@ -138,7 +168,7 @@ class HTTPConnection(_HTTPConnection, object):
|
||||
|
||||
try:
|
||||
conn = connection.create_connection(
|
||||
(self.host, self.port), self.timeout, **extra_kw)
|
||||
(self._dns_host, self.port), self.timeout, **extra_kw)
|
||||
|
||||
except SocketTimeout as e:
|
||||
raise ConnectTimeoutError(
|
||||
|
||||
+14
-13
@@ -40,13 +40,10 @@ from .util.request import set_file_position
|
||||
from .util.response import assert_header_parsing
|
||||
from .util.retry import Retry
|
||||
from .util.timeout import Timeout
|
||||
from .util.url import get_host, Url
|
||||
from .util.url import get_host, Url, NORMALIZABLE_SCHEMES
|
||||
from .util.queue import LifoQueue
|
||||
|
||||
|
||||
if six.PY2:
|
||||
# Queue is imported for side effects on MS Windows
|
||||
import Queue as _unused_module_Queue # noqa: F401
|
||||
|
||||
xrange = six.moves.xrange
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -62,13 +59,13 @@ class ConnectionPool(object):
|
||||
"""
|
||||
|
||||
scheme = None
|
||||
QueueCls = queue.LifoQueue
|
||||
QueueCls = LifoQueue
|
||||
|
||||
def __init__(self, host, port=None):
|
||||
if not host:
|
||||
raise LocationValueError("No host specified.")
|
||||
|
||||
self.host = _ipv6_host(host).lower()
|
||||
self.host = _ipv6_host(host, self.scheme)
|
||||
self._proxy_host = host.lower()
|
||||
self.port = port
|
||||
|
||||
@@ -204,8 +201,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
Return a fresh :class:`HTTPConnection`.
|
||||
"""
|
||||
self.num_connections += 1
|
||||
log.debug("Starting new HTTP connection (%d): %s",
|
||||
self.num_connections, self.host)
|
||||
log.debug("Starting new HTTP connection (%d): %s:%s",
|
||||
self.num_connections, self.host, self.port or "80")
|
||||
|
||||
conn = self.ConnectionCls(host=self.host, port=self.port,
|
||||
timeout=self.timeout.connect_timeout,
|
||||
@@ -411,6 +408,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
"""
|
||||
Close all pooled connections and disable the pool.
|
||||
"""
|
||||
if self.pool is None:
|
||||
return
|
||||
# Disable access to the pool
|
||||
old_pool, self.pool = self.pool, None
|
||||
|
||||
@@ -434,7 +433,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
# TODO: Add optional support for socket.gethostbyname checking.
|
||||
scheme, host, port = get_host(url)
|
||||
|
||||
host = _ipv6_host(host).lower()
|
||||
host = _ipv6_host(host, self.scheme)
|
||||
|
||||
# Use explicit default port for comparison when none is given
|
||||
if self.port and not port:
|
||||
@@ -820,8 +819,8 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
Return a fresh :class:`httplib.HTTPSConnection`.
|
||||
"""
|
||||
self.num_connections += 1
|
||||
log.debug("Starting new HTTPS connection (%d): %s",
|
||||
self.num_connections, self.host)
|
||||
log.debug("Starting new HTTPS connection (%d): %s:%s",
|
||||
self.num_connections, self.host, self.port or "443")
|
||||
|
||||
if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
|
||||
raise SSLError("Can't connect to HTTPS URL because the SSL "
|
||||
@@ -886,7 +885,7 @@ def connection_from_url(url, **kw):
|
||||
return HTTPConnectionPool(host, port=port, **kw)
|
||||
|
||||
|
||||
def _ipv6_host(host):
|
||||
def _ipv6_host(host, scheme):
|
||||
"""
|
||||
Process IPv6 address literals
|
||||
"""
|
||||
@@ -902,4 +901,6 @@ def _ipv6_host(host):
|
||||
# percent sign might be URIencoded, convert it back into ASCII
|
||||
if host.startswith('[') and host.endswith(']'):
|
||||
host = host.replace('%25', '%').strip('[]')
|
||||
if scheme in NORMALIZABLE_SCHEMES:
|
||||
host = host.lower()
|
||||
return host
|
||||
|
||||
+5
-2
@@ -111,6 +111,9 @@ def _cert_array_from_pem(pem_bundle):
|
||||
Given a bundle of certs in PEM format, turns them into a CFArray of certs
|
||||
that can be used to validate a cert chain.
|
||||
"""
|
||||
# Normalize the PEM bundle's line endings.
|
||||
pem_bundle = pem_bundle.replace(b"\r\n", b"\n")
|
||||
|
||||
der_certs = [
|
||||
base64.b64decode(match.group(1))
|
||||
for match in _PEM_CERTS_RE.finditer(pem_bundle)
|
||||
@@ -183,8 +186,8 @@ def _temporary_keychain():
|
||||
# some random bytes to password-protect the keychain we're creating, so we
|
||||
# ask for 40 random bytes.
|
||||
random_bytes = os.urandom(40)
|
||||
filename = base64.b64encode(random_bytes[:8]).decode('utf-8')
|
||||
password = base64.b64encode(random_bytes[8:]) # Must be valid UTF-8
|
||||
filename = base64.b16encode(random_bytes[:8]).decode('utf-8')
|
||||
password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8
|
||||
tempdirectory = tempfile.mkdtemp()
|
||||
|
||||
keychain_path = os.path.join(tempdirectory, filename).encode('utf-8')
|
||||
|
||||
+10
-1
@@ -236,15 +236,24 @@ class AppEngineManager(RequestMethods):
|
||||
encodings.remove('chunked')
|
||||
urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings)
|
||||
|
||||
return HTTPResponse(
|
||||
original_response = HTTPResponse(
|
||||
# In order for decoding to work, we must present the content as
|
||||
# a file-like object.
|
||||
body=BytesIO(urlfetch_resp.content),
|
||||
msg=urlfetch_resp.header_msg,
|
||||
headers=urlfetch_resp.headers,
|
||||
status=urlfetch_resp.status_code,
|
||||
**response_kw
|
||||
)
|
||||
|
||||
return HTTPResponse(
|
||||
body=BytesIO(urlfetch_resp.content),
|
||||
headers=urlfetch_resp.headers,
|
||||
status=urlfetch_resp.status_code,
|
||||
original_response=original_response,
|
||||
**response_kw
|
||||
)
|
||||
|
||||
def _get_absolute_timeout(self, timeout):
|
||||
if timeout is Timeout.DEFAULT_TIMEOUT:
|
||||
return None # Defer to URLFetch's default.
|
||||
|
||||
+12
-10
@@ -47,6 +47,12 @@ import OpenSSL.SSL
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends.openssl import backend as openssl_backend
|
||||
from cryptography.hazmat.backends.openssl.x509 import _Certificate
|
||||
try:
|
||||
from cryptography.x509 import UnsupportedExtension
|
||||
except ImportError:
|
||||
# UnsupportedExtension is gone in cryptography >= 2.1.0
|
||||
class UnsupportedExtension(Exception):
|
||||
pass
|
||||
|
||||
from socket import timeout, error as SocketError
|
||||
from io import BytesIO
|
||||
@@ -199,7 +205,7 @@ def get_subj_alt_name(peer_cert):
|
||||
except x509.ExtensionNotFound:
|
||||
# No such extension, return the empty list.
|
||||
return []
|
||||
except (x509.DuplicateExtension, x509.UnsupportedExtension,
|
||||
except (x509.DuplicateExtension, UnsupportedExtension,
|
||||
x509.UnsupportedGeneralNameType, UnicodeError) as e:
|
||||
# A problem has been found with the quality of the certificate. Assume
|
||||
# no SAN field is present.
|
||||
@@ -267,8 +273,7 @@ class WrappedSocket(object):
|
||||
else:
|
||||
raise
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
rd = util.wait_for_read(self.socket, self.socket.gettimeout())
|
||||
if not rd:
|
||||
if not util.wait_for_read(self.socket, self.socket.gettimeout()):
|
||||
raise timeout('The read operation timed out')
|
||||
else:
|
||||
return self.recv(*args, **kwargs)
|
||||
@@ -289,8 +294,7 @@ class WrappedSocket(object):
|
||||
else:
|
||||
raise
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
rd = util.wait_for_read(self.socket, self.socket.gettimeout())
|
||||
if not rd:
|
||||
if not util.wait_for_read(self.socket, self.socket.gettimeout()):
|
||||
raise timeout('The read operation timed out')
|
||||
else:
|
||||
return self.recv_into(*args, **kwargs)
|
||||
@@ -303,8 +307,7 @@ class WrappedSocket(object):
|
||||
try:
|
||||
return self.connection.send(data)
|
||||
except OpenSSL.SSL.WantWriteError:
|
||||
wr = util.wait_for_write(self.socket, self.socket.gettimeout())
|
||||
if not wr:
|
||||
if not util.wait_for_write(self.socket, self.socket.gettimeout()):
|
||||
raise timeout()
|
||||
continue
|
||||
except OpenSSL.SSL.SysCallError as e:
|
||||
@@ -418,7 +421,7 @@ class PyOpenSSLContext(object):
|
||||
self._ctx.load_verify_locations(BytesIO(cadata))
|
||||
|
||||
def load_cert_chain(self, certfile, keyfile=None, password=None):
|
||||
self._ctx.use_certificate_file(certfile)
|
||||
self._ctx.use_certificate_chain_file(certfile)
|
||||
if password is not None:
|
||||
self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password)
|
||||
self._ctx.use_privatekey_file(keyfile or certfile)
|
||||
@@ -440,8 +443,7 @@ class PyOpenSSLContext(object):
|
||||
try:
|
||||
cnx.do_handshake()
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
rd = util.wait_for_read(sock, sock.gettimeout())
|
||||
if not rd:
|
||||
if not util.wait_for_read(sock, sock.gettimeout()):
|
||||
raise timeout('select timed out')
|
||||
continue
|
||||
except OpenSSL.SSL.Error as e:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user