Merge branch 'master' into master

This commit is contained in:
Dan Ryan
2019-03-06 19:00:10 -05:00
committed by GitHub
206 changed files with 14817 additions and 4784 deletions
+2 -1
View File
@@ -16,8 +16,9 @@ steps:
export PIP_PROCESS_DEPENDENCY_LINKS="1"
echo "Path $PATH"
echo "Installing Pipenv…"
pip install -e "$(pwd)" --upgrade
pip install -e "$(pwd)[test]" --upgrade
pipenv install --deploy --dev
pipenv run pip install -e "$(pwd)[test]" --upgrade
echo pipenv --venv && echo pipenv --py && echo pipenv run python --version
displayName: Make Virtualenv
+2 -3
View File
@@ -10,9 +10,8 @@ jobs:
maxParallel: 4
matrix:
${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
# TODO remove once vs2017-win2016 has Python 3.7
Python37:
python.version: '>= 3.7.0-b2'
python.version: '>= 3.7.2'
python.architecture: x64
${{ if ne(parameters.vmImage, 'vs2017-win2016' )}}:
Python37:
@@ -33,7 +32,7 @@ jobs:
pip install certifi
export GIT_SSL_CAINFO=$(python -m certifi)
export LANG=C.UTF-8
python -m pip install --upgrade invoke requests parver bs4 vistir towncrier
python -m pip install --upgrade invoke requests parver bs4 vistir towncrier pip setuptools wheel --upgrade-strategy=eager
python -m invoke vendoring.update
- template: ./run-manifest-check.yml
+1 -1
View File
@@ -1,6 +1,6 @@
steps:
- script: |
virtualenv D:\.venv
D:\.venv\Scripts\pip.exe install -e . && D:\.venv\Scripts\pipenv install --dev
D:\.venv\Scripts\pip.exe install -e .[test] && D:\.venv\Scripts\pipenv install --dev && D:\.venv\Scripts\pipenv run pip install -e .[test]
echo D:\.venv\Scripts\pipenv --venv && echo D:\.venv\Scripts\pipenv --py && echo D:\.venv\Scripts\pipenv run python --version
displayName: Make Virtualenv
@@ -1,3 +1,3 @@
steps:
- script: 'python -m pip install --upgrade pip && python -m pip install -e .'
- script: 'python -m pip install --upgrade pip && python -m pip install -e .[test]'
displayName: Upgrade Pip & Install Pipenv
+5
View File
@@ -153,3 +153,8 @@ venv.bak/
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
.vs/slnx.sqlite
# mypy/typing section
typeshed/
.dmypy.json
mypyhtml/
+2 -7
View File
@@ -1,16 +1,11 @@
[dev-packages]
pipenv = {path = ".", editable = true}
pipenv = {path = ".", editable = true, extras = ["test"]}
"flake8" = ">=3.3.0,<4"
pytest = "*"
mock = "*"
sphinx = "<=1.5.5"
twine = "*"
sphinx-click = "*"
pytest-xdist = "*"
click = "*"
pytest-pypy = {path = "./tests/pytest-pypi", editable = true}
pytest-tap = "*"
flaky = "*"
pytest-pypi = {path = "./tests/pytest-pypi", editable = true}
stdeb = {version="*", markers="sys_platform == 'linux'"}
black = {version="*", markers="python_version >= '3.6'"}
pytz = "*"
Generated
+207 -228
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f0a57ba6f180e7312f799a460a9d2790c9c26c3556eff2b5d1fe7d9b9174d05b"
"sha256": "d7119fe8fa7be8224ff46509352efbd76dd17accf6a57580dbaf5762e613468b"
},
"pipfile-spec": 6,
"requires": {},
@@ -36,14 +36,6 @@
],
"version": "==1.4.3"
},
"argparse": {
"hashes": [
"sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4",
"sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"
],
"markers": "python_version == '2.6'",
"version": "==1.4.0"
},
"arpeggio": {
"hashes": [
"sha256:a5258b84f76661d558492fa87e42db634df143685a0e51802d59cae7daad8732",
@@ -53,10 +45,10 @@
},
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
],
"version": "==1.2.1"
"version": "==1.3.0"
},
"attrs": {
"hashes": [
@@ -72,13 +64,37 @@
],
"version": "==2.6.0"
},
"backports.functools-lru-cache": {
"hashes": [
"sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a",
"sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd"
],
"markers": "python_version < '3'",
"version": "==1.5"
},
"backports.shutil-get-terminal-size": {
"hashes": [
"sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64",
"sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80"
],
"markers": "python_version < '3.3'",
"version": "==1.0.0"
},
"backports.weakref": {
"hashes": [
"sha256:81bc9b51c0abc58edc76aefbbc68c62a787918ffe943a37947e162c3f8e19e82",
"sha256:bc4170a29915f8b22c9e7c4939701859650f2eb84184aee80da329ac0b9825c2"
],
"markers": "python_version < '3.3'",
"version": "==1.0.post1"
},
"beautifulsoup4": {
"hashes": [
"sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57",
"sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10",
"sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938"
"sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858",
"sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348",
"sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718"
],
"version": "==4.6.3"
"version": "==4.7.1"
},
"black": {
"hashes": [
@@ -91,10 +107,10 @@
},
"bleach": {
"hashes": [
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
],
"version": "==3.0.2"
"version": "==3.1.0"
},
"bs4": {
"hashes": [
@@ -111,47 +127,10 @@
},
"certifi": {
"hashes": [
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.10.15"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"version": "==1.11.5"
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
@@ -168,39 +147,6 @@
"index": "pypi",
"version": "==7.0"
},
"cmarkgfm": {
"hashes": [
"sha256:0186dccca79483e3405217993b83b914ba4559fe9a8396efc4eea56561b74061",
"sha256:1a625afc6f62da428df96ec325dc30866cc5781520cbd904ff4ec44cf018171c",
"sha256:207b7673ff4e177374c572feeae0e4ef33be620ec9171c08fd22e2b796e03e3d",
"sha256:275905bb371a99285c74931700db3f0c078e7603bed383e8cf1a09f3ee05a3de",
"sha256:50098f1c4950722521f0671e54139e0edc1837d63c990cf0f3d2c49607bb51a2",
"sha256:50ed116d0b60a07df0dc7b180c28569064b9d37d1578d4c9021cff04d725cb63",
"sha256:61a72def110eed903cd1848245897bcb80d295cd9d13944d4f9f30cba5b76655",
"sha256:64186fb75d973a06df0e6ea12879533b71f6e7ba1ab01ffee7fc3e7534758889",
"sha256:665303d34d7f14f10d7b0651082f25ebf7107f29ef3d699490cac16cdc0fc8ce",
"sha256:70b18f843aec58e4e64aadce48a897fe7c50426718b7753aaee399e72df64190",
"sha256:761ee7b04d1caee2931344ac6bfebf37102ffb203b136b676b0a71a3f0ea3c87",
"sha256:811527e9b7280b136734ed6cb6845e5fbccaeaa132ddf45f0246cbe544016957",
"sha256:987b0e157f70c72a84f3c2f9ef2d7ab0f26c08f2bf326c12c087ff9eebcb3ff5",
"sha256:9fc6a2183d0a9b0974ec7cdcdad42bd78a3be674cc3e65f87dd694419b3b0ab7",
"sha256:a3d17ee4ae739fe16f7501a52255c2e287ac817cfd88565b9859f70520afffea",
"sha256:ba5b5488719c0f2ced0aa1986376f7baff1a1653a8eb5fdfcf3f84c7ce46ef8d",
"sha256:c573ea89dd95d41b6d8cf36799c34b6d5b1eac4aed0212dee0f0a11fb7b01e8f",
"sha256:c5f1b9e8592d2c448c44e6bc0d91224b16ea5f8293908b1561de1f6d2d0658b1",
"sha256:cbe581456357d8f0674d6a590b1aaf46c11d01dd0a23af147a51a798c3818034",
"sha256:cf219bec69e601fe27e3974b7307d2f06082ab385d42752738ad2eb630a47d65",
"sha256:cf5014eb214d814a83a7a47407272d5db10b719dbeaf4d3cfe5969309d0fcf4b",
"sha256:d08bad67fa18f7e8ff738c090628ee0cbf0505d74a991c848d6d04abfe67b697",
"sha256:d6f716d7b1182bf35862b5065112f933f43dd1aa4f8097c9bcfb246f71528a34",
"sha256:e08e479102627641c7cb4ece421c6ed4124820b1758765db32201136762282d9",
"sha256:e20ac21418af0298437d29599f7851915497ce9f2866bc8e86b084d8911ee061",
"sha256:e25f53c37e319241b9a412382140dffac98ca756ba8f360ac7ab5e30cad9670a",
"sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09",
"sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c"
],
"version": "==0.4.2"
},
"colorama": {
"hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
@@ -210,16 +156,18 @@
},
"configparser": {
"hashes": [
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
"sha256:5bd5fa2a491dc3cfe920a3f2a107510d65eceae10e9c6e547b90261a4710df32",
"sha256:c114ff90ee2e762db972fa205f02491b1f5cf3ff950decd8542c62970c9bedac",
"sha256:df28e045fbff307a28795b18df6ac8662be3219435560ddb068c283afab1ea7a"
],
"markers": "python_version < '3.2'",
"version": "==3.5.0"
"version": "==3.7.1"
},
"cursor": {
"hashes": [
"sha256:8ee9fe5b925e1001f6ae6c017e93682583d2b4d1ef7130a26cfcdf1651c0032c"
"sha256:7e728934f555a84a1c8b0850b66efcb580d092acc927b7d15dd43eb27dd4c4c5"
],
"version": "==1.2.0"
"version": "==1.3.1"
},
"distlib": {
"hashes": [
@@ -235,6 +183,13 @@
],
"version": "==0.14"
},
"entrypoints": {
"hashes": [
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
],
"version": "==0.3"
},
"enum34": {
"hashes": [
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
@@ -242,7 +197,7 @@
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
],
"markers": "python_version < '3.4'",
"markers": "python_version < '3'",
"version": "==1.1.6"
},
"execnet": {
@@ -261,19 +216,18 @@
},
"flake8": {
"hashes": [
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
"sha256:c3ba1e130c813191db95c431a18cb4d20a468e98af7a77e2181b68574481ad36",
"sha256:fd9ddf503110bf3d8b1d270e8c673aab29ccb3dd6abf29bae1f54e5116ab4a91"
],
"index": "pypi",
"version": "==3.5.0"
"version": "==3.7.5"
},
"flaky": {
"hashes": [
"sha256:4ad7880aef8c35a34ddb394d4fa33047765bca1e3d67d182bf6eba9c8eabf3a2",
"sha256:d0533f473a46b916e6db6e84e20b06d8a70656600a0c14e819b0760b63f70226"
"sha256:12bd5e41f372b2190e8d754b6e5829c2f11dbc764e10b30f57e59f829c9ca1da",
"sha256:a94931c46a33469ec26f09b652bc88f55a8f5cc77807b90ca7bbafef1108fd7d"
],
"index": "pypi",
"version": "==3.4.0"
"version": "==3.5.3"
},
"flask": {
"hashes": [
@@ -287,29 +241,31 @@
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
],
"markers": "python_version < '3.3'",
"markers": "python_version < '3.0'",
"version": "==1.0.2"
},
"future": {
"functools32": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
"sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0",
"sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"
],
"version": "==0.16.0"
"markers": "python_version >= '2.7' and python_version < '2.8'",
"version": "==3.2.3.post2"
},
"futures": {
"hashes": [
"sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265",
"sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1"
],
"markers": "python_version < '3' and python_version >= '2.6'",
"markers": "python_version < '3.0'",
"version": "==3.2.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.7"
"version": "==2.8"
},
"imagesize": {
"hashes": [
@@ -352,11 +308,11 @@
},
"jedi": {
"hashes": [
"sha256:0191c447165f798e6a730285f2eee783fff81b0d3df261945ecb80983b5c3ca7",
"sha256:b7493f73a2febe0dc33d51c99b474547f7f6c0b2c8fb2b21f453eef204c12148"
"sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd",
"sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191"
],
"index": "pypi",
"version": "==0.13.1"
"version": "==0.13.2"
},
"jinja2": {
"hashes": [
@@ -410,57 +366,66 @@
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
],
"index": "pypi",
"version": "==2.0.0"
},
"more-itertools": {
"hashes": [
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
"sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4",
"sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc",
"sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"
],
"version": "==4.3.0"
"markers": "python_version <= '2.7'",
"version": "==5.0.0"
},
"packaging": {
"hashes": [
"sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
"sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
],
"version": "==18.0"
"version": "==19.0"
},
"parso": {
"hashes": [
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
"sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"
"sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9",
"sha256:68406ebd7eafe17f8e40e15a84b56848eccbf27d7c1feb89e93d8fca395706db"
],
"version": "==0.3.1"
"version": "==0.3.4"
},
"parver": {
"hashes": [
"sha256:ac4afff688d19d5e1876bb68d4bccc1a1b6a5cc8bd6a646939a14d366695ba15",
"sha256:f025fba8f88a9c776971df6d62b6cf7f37d1108f84c163bda91e157d7d527075"
"sha256:1b37a691af145a3a193eff269d53ba5b2ab16dfbb65d47d85360755919f5fe4b",
"sha256:72d056b8f8883ac90eef5554a9c8a47fac39d3b66479f3d2c8d5bc21b849cdba"
],
"index": "pypi",
"version": "==0.1.1"
"version": "==0.2.1"
},
"passa": {
"editable": true,
"git": "https://github.com/sarugaku/passa.git",
"ref": "4f3b8102f122cf0b75e5d7c513a2e61b0b093dcd"
"ref": "a2ba0b30c86339cae5ef3a03046fc9c583452c40",
"version": "==0.3.1.dev0"
},
"pathlib2": {
"hashes": [
"sha256:25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742",
"sha256:5887121d7f7df3603bca2f710e7219f3eca0eb69e0b7cc6e0a022e155ac931a7"
],
"markers": "python_version < '3.5'",
"version": "==2.3.3"
},
"pbr": {
"hashes": [
"sha256:f59d71442f9ece3dffc17bc36575768e1ee9967756e6b6535f0ee1f0054c3d68",
"sha256:f6d5b23f226a2ba58e14e49aa3b1bfaf814d0199144b95d78458212444de1387"
"sha256:a7953f66e1f82e4b061f43096a4bcc058f7d3d41de9b94ac871770e8bdd831a2",
"sha256:d717573351cfe09f49df61906cd272abaa759b3e91744396b804965ff7bff38b"
],
"version": "==5.1.1"
"version": "==5.1.2"
},
"pep517": {
"hashes": [
"sha256:cc663a438fdfe2e88d8d3c5ef2203ac858de34e31b6609b1fc505d611490a926",
"sha256:f79bb08fb064dfc5b141204bfeb56a4141a6d504677fab4723036a464fc25cc1"
"sha256:43a7aa3902efd305a605c1028e4045968cd012831233ecab633a31d3ba4860a5",
"sha256:cb5ca55450b64e80744cd5c32f7d5b8928004042dfea50fdc3f96ad7f27cba96"
],
"version": "==0.3"
"version": "==0.5.0"
},
"pip-shims": {
"hashes": [
@@ -471,14 +436,15 @@
},
"pipenv": {
"editable": true,
"path": "."
"path": ".",
"version": "==2018.11.27.dev0"
},
"pkginfo": {
"hashes": [
"sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474",
"sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"
"sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb",
"sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"
],
"version": "==1.4.2"
"version": "==1.5.0.1"
},
"plette": {
"extras": [
@@ -492,10 +458,10 @@
},
"pluggy": {
"hashes": [
"sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
"sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
"sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616",
"sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a"
],
"version": "==0.8.0"
"version": "==0.8.1"
},
"py": {
"hashes": [
@@ -506,72 +472,64 @@
},
"pycodestyle": {
"hashes": [
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
"sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a"
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
],
"version": "==2.4.0"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
"version": "==2.5.0"
},
"pyflakes": {
"hashes": [
"sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49",
"sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae"
"sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d",
"sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd"
],
"version": "==2.0.0"
"version": "==2.1.0"
},
"pygments": {
"hashes": [
"sha256:6301ecb0997a52d2d31385e62d0a4a4cf18d2f2da7054a5ddad5c366cd39cee7",
"sha256:82666aac15622bd7bb685a4ee7f6625dd716da3ef7473620c192c0168aae64fc"
"sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a",
"sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"
],
"version": "==2.3.0"
"version": "==2.3.1"
},
"pyparsing": {
"hashes": [
"sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
"sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
"sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a",
"sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3"
],
"version": "==2.3.0"
"version": "==2.3.1"
},
"pytest": {
"hashes": [
"sha256:7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e",
"sha256:9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"
"sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec",
"sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"
],
"index": "pypi",
"version": "==3.8.2"
"version": "==3.10.1"
},
"pytest-forked": {
"hashes": [
"sha256:e4500cd0509ec4a26535f7d4112a8cc0f17d3a41c29ffd4eab479d2a55b30805",
"sha256:f275cb48a73fc61a6710726348e1da6d68a978f0ec0c54ece5a5fae5977e5a08"
"sha256:5fe33fbd07d7b1302c95310803a5e5726a4ff7f19d5a542b7ce57c76fed8135f",
"sha256:d352aaced2ebd54d42a65825722cb433004b4446ab5d2044851d9cc7a00c9e38"
],
"version": "==0.2"
"version": "==1.0.2"
},
"pytest-pypy": {
"pytest-pypi": {
"editable": true,
"path": "./tests/pytest-pypi"
"path": "./tests/pytest-pypi",
"version": "==0.1.1"
},
"pytest-tap": {
"hashes": [
"sha256:3b05ec931424bbe44e944726b68f7ef185bb6d25ce9ce21ac52c9af7ffa9b506",
"sha256:ca063de56298034302f3cbce55c87a27d7bfa7af7de591cdb9ec6ce45fea5467"
],
"index": "pypi",
"version": "==2.3"
},
"pytest-xdist": {
"hashes": [
"sha256:06aa39361694c9365baaa03bec71159b59ad06c9826c6279ebba368cb3571561",
"sha256:1ef0d05c905cfa0c5442c90e9e350e65c6ada120e33a00a066ca51c89f5f869a"
"sha256:4a201bb3ee60f5dd6bb40c5209d4e491cecc4d5bafd656cfb10f86178786e568",
"sha256:d03d1ff1b008458ed04fa73e642d840ac69b4107c168e06b71037c62d7813dd4"
],
"index": "pypi",
"version": "==1.23.2"
"version": "==1.26.1"
},
"pytoml": {
"hashes": [
@@ -581,11 +539,11 @@
},
"pytz": {
"hashes": [
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
],
"index": "pypi",
"version": "==2018.5"
"version": "==2018.9"
},
"readme-renderer": {
"hashes": [
@@ -596,24 +554,24 @@
},
"requests": {
"hashes": [
"sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54",
"sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263"
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
],
"version": "==2.20.1"
"version": "==2.21.0"
},
"requests-toolbelt": {
"hashes": [
"sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237",
"sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
],
"version": "==0.8.0"
"version": "==0.9.1"
},
"requirementslib": {
"hashes": [
"sha256:c2c00c7bd3bd4984c97d10cd4d143efbe33b5ed9e55961bea30ca7a9a4927289",
"sha256:dc6b692e8dee03d6e90c29db1e337b0bf8152cce84a57f0fb4765e596afde4e0"
"sha256:c26feee79853dedddab550cf79fb2fa83b4bc1a16eab58f2c870e8314caa6cc5",
"sha256:d302b780afbd1d60f49d368b535929d8ff4b6d972797f3777c9560d48abdded7"
],
"version": "==1.3.3"
"version": "==1.4.0"
},
"resolvelib": {
"hashes": [
@@ -624,17 +582,34 @@
},
"rope": {
"hashes": [
"sha256:a108c445e1cd897fe19272ab7877d172e7faf3d4148c80e7d20faba42ea8f7b2"
"sha256:031eb54b3eeec89f4304ede816995ed2b93a21e6fba16bd02aff10a0d6c257b7"
],
"index": "pypi",
"version": "==0.11.0"
"version": "==0.12.0"
},
"scandir": {
"hashes": [
"sha256:04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6",
"sha256:1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e",
"sha256:1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6",
"sha256:346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e",
"sha256:44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064",
"sha256:61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792",
"sha256:a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a",
"sha256:c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383",
"sha256:c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b",
"sha256:c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8",
"sha256:f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"
],
"markers": "python_version < '3.5'",
"version": "==1.9.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.11.0"
"version": "==1.12.0"
},
"snowballstemmer": {
"hashes": [
@@ -643,6 +618,13 @@
],
"version": "==1.2.1"
},
"soupsieve": {
"hashes": [
"sha256:afa56bf14907bb09403e5d15fbed6275caa4174d36b975226e3b67a3bb6e2c4b",
"sha256:eaed742b48b1f3e2d45ba6f79401b2ed5dc33b2123dfe216adb90d4bfa0ade26"
],
"version": "==1.8"
},
"sphinx": {
"hashes": [
"sha256:11f271e7a9398385ed730e90f0bb41dc3815294bdcd395b46ed2d033bc2e7d87",
@@ -653,18 +635,11 @@
},
"sphinx-click": {
"hashes": [
"sha256:0550d3e5dcd6244847bd0861ebe64101a2ef302913866e0ccd9095b2aa230051",
"sha256:404784f724504e3da2cb056767ba64955c4bfb9bfca8cfedd7142a962bafd70f"
"sha256:926da1a7c677ae1b35cf255269ff84fec65d0f92e4863acfa77b92cf8ae32275",
"sha256:f0c03d6ea0e4258c9c09646b6f745090ea8dd13e7e045903e4b789dfc02f7846"
],
"index": "pypi",
"version": "==1.3.0"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
],
"version": "==1.1.0"
"version": "==2.0.1"
},
"stdeb": {
"hashes": [
@@ -684,7 +659,8 @@
"toml": {
"hashes": [
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e",
"sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"
],
"version": "==0.10.0"
},
@@ -698,31 +674,32 @@
"towncrier": {
"editable": true,
"git": "https://github.com/hawkowl/towncrier.git",
"ref": "47754a607a9b03f06affaf167d65b990786aae25"
"ref": "ecd438c9c0ef132a92aba2eecc4dc672ccf9ec63",
"version": "==19.2.0"
},
"tqdm": {
"hashes": [
"sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392",
"sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb"
"sha256:d385c95361699e5cf7622485d9b9eae2d4864b21cd5a2374a9c381ffed701021",
"sha256:e22977e3ebe961f72362f6ddfb9197cc531c9737aaf5f607ef09740c849ecd05"
],
"version": "==4.28.1"
"version": "==4.31.1"
},
"twine": {
"hashes": [
"sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c",
"sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c"
"sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446",
"sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"
],
"index": "pypi",
"version": "==1.12.1"
"version": "==1.13.0"
},
"typing": {
"hashes": [
"sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf",
"sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8",
"sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2"
"sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d",
"sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4",
"sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a"
],
"markers": "python_version < '3.5'",
"version": "==3.6.4"
"version": "==3.6.6"
},
"urllib3": {
"hashes": [
@@ -733,26 +710,28 @@
},
"virtualenv": {
"hashes": [
"sha256:686176c23a538ecc56d27ed9d5217abd34644823d6391cbeb232f42bf722baad",
"sha256:f899fafcd92e1150f40c8215328be38ff24b519cd95357fa6e78e006c7638208"
"sha256:8b9abfc51c38b70f61634bf265e5beacf6fae11fc25d355d1871f49b8e45f0db",
"sha256:cceab52aa7d4df1e1871a70236eb2b89fcfe29b6b43510d9738689787c513261"
],
"version": "==16.1.0"
"version": "==16.4.0"
},
"virtualenv-clone": {
"hashes": [
"sha256:afce268508aa5596c90dda234abe345deebc401a57d287bcbd76baa140a1aa58"
"sha256:217bd3f0880c9f85672c0bcc9ad9e0354ab7dfa89c2f117e63aa878b4279f5bf",
"sha256:316c8a05432a7adb5e461709759aca18c51433ffc2c33e2e80c9e51c452d339f",
"sha256:f2a07ed255f3abaceef8c8442512d8cdb2ba9f867e212d8a51680c7790a85033"
],
"version": "==0.4.0"
"version": "==0.5.1"
},
"vistir": {
"extras": [
"spinner"
],
"hashes": [
"sha256:3a1020fb7be000b268af96641ced9ead844b1f75840c41e20e473647688fc630",
"sha256:6d2005ad670f77bd9c9b5415c4e2a4a20dce5b0cf0e0d11598eb463b2e0ebe44"
"sha256:510408ec63a4b423967fd630bf0885c8d6a1d5d126f8bb1be6aba86a0da5e815",
"sha256:fc5cca7a14e92feaa6f85dd91da74d834904280a96a21190aecb4cd1d1048e0e"
],
"version": "==0.2.5"
"version": "==0.3.0"
},
"webencodings": {
"hashes": [
@@ -770,17 +749,17 @@
},
"wheel": {
"hashes": [
"sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6",
"sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44"
"sha256:12363e6df5678ecf9daf8429f06f97e7106e701405898f24318ce7f0b79c611a",
"sha256:b79ffea026bc0dbd940868347ae9eee36789b6496b6623bd2dec7c7c540a8f99"
],
"version": "==0.32.3"
"version": "==0.33.0"
},
"yaspin": {
"hashes": [
"sha256:36fdccc5e0637b5baa8892fe2c3d927782df7d504e9020f40eb2c1502518aa5a",
"sha256:8e52bf8079a48e2a53f3dfeec9e04addb900c101d1591c85df69cf677d3237e7"
"sha256:441f8a6761e347652d04614899fd0a9cfda7439e2d5682e664bd31230c656176",
"sha256:d3ebcf8162e0ef8bb5484b8751d5b6d2fbf0720112c81f64614c308576a03b1d"
],
"version": "==0.14.0"
"version": "==0.14.1"
}
}
}
+1
View File
@@ -0,0 +1 @@
Fixed a bug which caused editable package resolution to sometimes fail with an unhelpful setuptools-related error message.
+1
View File
@@ -0,0 +1 @@
Dependency resolution now writes hashes for local and remote files to the lockfile.
+1
View File
@@ -0,0 +1 @@
Fixed a bug which prevented ``pipenv graph`` from correctly showing all dependencies when running from within ``pipenv shell``.
+1
View File
@@ -0,0 +1 @@
Fixed resolution of direct-url dependencies in ``setup.py`` files to respect ``PEP-508`` style URL dependencies.
+1
View File
@@ -0,0 +1 @@
Added support for resolution of direct-url dependencies in ``setup.py`` files to respect ``PEP-508`` style URL dependencies.
+3
View File
@@ -0,0 +1,3 @@
Fixed a bug which caused failures in warning reporting when running pipenv inside a virtualenv under some circumstances.
- Fixed a bug with package discovery when running ``pipenv clean``.
+5
View File
@@ -0,0 +1,5 @@
Added full support for resolution of all dependency types including direct URLs, zip archives, tarballs, etc.
- Improved error handling and formatting.
- Introduced improved cross platform stream wrappers for better ``stdout`` and ``stderr`` consistency.
+28
View File
@@ -0,0 +1,28 @@
Updated vendored dependencies:
- **attrs**: ``18.2.0`` => ``19.1.0``
- **certifi**: ``2018.10.15`` => ``2018.11.29``
- **cached_property**: ``1.4.3`` => ``1.5.1``
- **colorama**: ``0.3.9`` => ``0.4.1``
- **idna**: ``2.7`` => ``2.8``
- **markupsafe**: ``1.0`` => ``1.1.1``
- **orderedmultidict**: ``(new)`` => ``1.0``
- **packaging**: ``18.0`` => ``19.0``
- **parse**: ``1.9.0`` => ``1.11.1``
- **pathlib2**: ``2.3.2`` => ``2.3.3``
- **pep517**: ``(new)`` => ``0.5.0``
- **pipdeptree**: ``0.13.0`` => ``0.13.2``
- **pyparsing**: ``2.2.2`` => ``2.3.1``
- **python-dotenv**: ``0.9.1`` => ``0.10.1``
- **pythonfinder**: ``1.1.10`` => ``1.2.0``
- **pytoml**: ``(new)`` => ``0.1.20``
- **requests**: ``2.20.1`` => ``2.21.0``
- **requirementslib**: ``1.3.3`` => ``1.4.2``
- **shellingham**: ``1.2.7`` => ``1.2.8``
- **six**: ``1.11.0`` => ``1.12.0``
- **tomlkit**: ``0.5.2`` => ``0.5.3``
- **urllib3**: ``1.24`` => ``1.24.1``
- **vistir**: ``0.3.0`` => ``0.3.1``
- **yaspin**: ``0.14.0`` => ``0.14.1``
- Removed vendored dependency **cursor**.
+1
View File
@@ -0,0 +1 @@
Pipenv will now successfully recursively lock VCS sub-dependencies.
+1
View File
@@ -0,0 +1 @@
Pipenv will now discover and resolve the intrinsic dependencies of **all** VCS dependencies, whether they are editable or not, to prevent resolution conflicts.
+1
View File
@@ -0,0 +1 @@
Fixed a keyerror which could occur when locking VCS dependencies in some cases.
+1 -1
View File
@@ -1 +1 @@
Fix a bug that ``ValidationError`` is thrown when some fields are missing in source section.
Fixed a bug that ``ValidationError`` is thrown when some fields are missing in source section.
+1 -1
View File
@@ -1 +1 @@
Fix the wrong order of old and new hashes in message.
Fixed the wrong order of old and new hashes in message.
+1 -1
View File
@@ -1 +1 @@
Update the index names in lock file when source name in Pipfile is changed.
Updated the index names in lock file when source name in Pipfile is changed.
+1
View File
@@ -0,0 +1 @@
Fix the issue that lock file can't be created when ``PIPENV_PIPFILE`` is not under working directory.
+6 -3
View File
@@ -5,9 +5,6 @@ Exposes a standard API that enables compatibility across python versions,
operating systems, etc.
"""
import functools
import importlib
import io
import os
import sys
import warnings
@@ -122,6 +119,12 @@ UNICODE_TO_ASCII_TRANSLATION_MAP = {
}
def decode_for_output(output, target=sys.stdout):
return vistir.misc.decode_for_output(
output, sys.stdout, translation_map=UNICODE_TO_ASCII_TRANSLATION_MAP
)
def decode_output(output):
if not isinstance(output, six.string_types):
return output
+3 -7
View File
@@ -12,8 +12,6 @@ import click_completion
import crayons
import delegator
from click_didyoumean import DYMCommandCollection
from ..__version__ import __version__
from .options import (
CONTEXT_SETTINGS, PipenvGroup, code_option, common_options, deploy_option,
@@ -300,6 +298,7 @@ def uninstall(
if retcode:
sys.exit(retcode)
@cli.command(short_help="Generates Pipfile.lock.", context_settings=CONTEXT_SETTINGS)
@lock_options
@pass_state
@@ -400,7 +399,6 @@ def shell(
def run(state, command, args):
"""Spawns a command installed into the virtualenv."""
from ..core import do_run
do_run(
command=command, args=args, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror
)
@@ -629,11 +627,9 @@ def sync(
def clean(ctx, state, dry_run=False, bare=False, user=False):
"""Uninstalls all packages not specified in Pipfile.lock."""
from ..core import do_clean
do_clean(ctx=ctx, three=state.three, python=state.python, dry_run=dry_run)
do_clean(ctx=ctx, three=state.three, python=state.python, dry_run=dry_run,
system=state.system)
# Only invoke the "did you mean" when an argument wasn't passed (it breaks those).
if "-" not in "".join(sys.argv) and len(sys.argv) > 1:
cli = DYMCommandCollection(sources=[cli])
if __name__ == "__main__":
cli()
+2 -1
View File
@@ -8,6 +8,7 @@ import click.types
from click import (
BadParameter, Group, Option, argument, echo, make_pass_decorator, option
)
from click_didyoumean import DYMMixin
from .. import environments
from ..utils import is_valid_url
@@ -19,7 +20,7 @@ CONTEXT_SETTINGS = {
}
class PipenvGroup(Group):
class PipenvGroup(DYMMixin, Group):
"""Custom Group class provides formatted main help"""
def get_help_option(self, ctx):
+166 -58
View File
@@ -1,5 +1,5 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
import json as simplejson
import logging
import os
@@ -11,7 +11,7 @@ import warnings
import click
import six
import urllib3.util as urllib3_util
import vistir
from pipenv.vendor import vistir
import click_completion
import crayons
@@ -20,7 +20,7 @@ import dotenv
import pipfile
from . import environments, exceptions, pep508checker, progress
from ._compat import fix_utf8
from ._compat import fix_utf8, decode_for_output
from .cmdparse import Script
from .environments import (
PIPENV_CACHE_DIR, PIPENV_COLORBLIND, PIPENV_DEFAULT_PYTHON_VERSION,
@@ -675,7 +675,7 @@ def batch_install(deps_list, procs, failed_deps_queue,
requirements_dir, no_deps=False, ignore_hashes=False,
allow_global=False, blocking=False, pypi_mirror=None,
nprocs=PIPENV_MAX_SUBPROCESS, retry=True):
from .vendor.requirementslib.models.utils import strip_extras_markers_from_requirement
failed = (not retry)
if not failed:
label = INSTALL_LABEL if os.name != "nt" else ""
@@ -690,6 +690,10 @@ def batch_install(deps_list, procs, failed_deps_queue,
trusted_hosts = []
# Install these because
for dep in deps_list_bar:
if dep.req.req:
dep.req.req = strip_extras_markers_from_requirement(dep.req.req)
if dep.markers:
dep.markers = str(strip_extras_markers_from_requirement(dep.get_markers()))
index = None
if dep.index:
index = project.find_source(dep.index)
@@ -698,10 +702,18 @@ def batch_install(deps_list, procs, failed_deps_queue,
trusted_hosts.append(urllib3_util.parse_url(index.get("url")).host)
# Install the module.
is_artifact = False
if no_deps:
link = getattr(dep.req, "link", None)
is_wheel = False
if link:
is_wheel = link.is_wheel
if dep.is_file_or_url and (dep.is_direct_url or any(
dep.req.uri.endswith(ext) for ext in ["zip", "tar.gz"]
)):
is_artifact = True
elif dep.is_vcs:
is_artifact = True
needs_deps = not no_deps if no_deps is True else is_artifact
extra_indexes = []
if not index and indexes:
@@ -714,24 +726,24 @@ def batch_install(deps_list, procs, failed_deps_queue,
os.environ["PIP_USER"] = vistir.compat.fs_str("0")
if "PYTHONHOME" in os.environ:
del os.environ["PYTHONHOME"]
if no_deps:
if not needs_deps:
link = getattr(dep.req, "link", None)
is_wheel = False
if link:
is_wheel = link.is_wheel
is_non_editable_vcs = (dep.is_vcs and not dep.editable)
no_deps = not (dep.is_file_or_url and not (is_wheel or dep.editable))
needs_deps = dep.is_file_or_url and not (is_wheel or dep.editable)
c = pip_install(
dep,
ignore_hashes=any([ignore_hashes, dep.editable, dep.is_vcs]),
allow_global=allow_global,
no_deps=no_deps,
no_deps=not needs_deps,
block=any([dep.editable, dep.is_vcs, blocking]),
index=index,
requirements_dir=requirements_dir,
pypi_mirror=pypi_mirror,
trusted_hosts=trusted_hosts,
extra_indexes=extra_indexes
extra_indexes=extra_indexes,
use_pep517=not retry,
)
if dep.is_vcs or dep.editable:
c.block()
@@ -814,6 +826,7 @@ def do_install_dependencies(
else:
install_kwargs["nprocs"] = 1
# with project.environment.activated():
batch_install(
deps_list, procs, failed_deps_queue, requirements_dir, **install_kwargs
)
@@ -1016,9 +1029,12 @@ def do_lock(
dev_packages = overwrite_dev(project.packages, dev_packages)
# Resolve dev-package dependencies, with pip-tools.
for is_dev in [True, False]:
pipfile_section = "dev_packages" if is_dev else "packages"
pipfile_section = "dev-packages" if is_dev else "packages"
lockfile_section = "develop" if is_dev else "default"
packages = getattr(project, pipfile_section)
if project.pipfile_exists:
packages = project.parsed_pipfile.get(pipfile_section, {})
else:
packages = getattr(project, pipfile_section.replace("-", "_"))
if write:
# Alert the user of progress.
@@ -1031,12 +1047,8 @@ def do_lock(
err=True,
)
deps = convert_deps_to_pip(
packages, project, r=False, include_index=True
)
# Mutates the lockfile
venv_resolve_deps(
deps,
packages,
which=which,
project=project,
dev=is_dev,
@@ -1257,12 +1269,12 @@ def pip_install(
requirements_dir=None,
extra_indexes=None,
pypi_mirror=None,
trusted_hosts=None
trusted_hosts=None,
use_pep517=True
):
from pipenv.patched.notpip._internal import logger as piplogger
from .utils import Mapping
from .vendor.vistir.compat import Mapping
from .vendor.urllib3.util import parse_url
src = []
write_to_tmpfile = False
if requirement:
@@ -1280,6 +1292,10 @@ def pip_install(
crayons.normal("Installing {0!r}".format(requirement.name), bold=True),
err=True,
)
if requirement:
ignore_hashes = True if not requirement.hashes else ignore_hashes
# Create files for hash mode.
if write_to_tmpfile:
if not requirements_dir:
@@ -1289,12 +1305,13 @@ def pip_install(
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir,
delete=False
)
f.write(vistir.misc.to_bytes(requirement.as_line()))
line = requirement.as_line(include_hashes=not ignore_hashes)
f.write(vistir.misc.to_bytes(line))
r = f.name
f.close()
# Install dependencies when a package is a VCS dependency.
if requirement and requirement.vcs:
no_deps = False
# Install dependencies when a package is a non-editable VCS dependency.
# Don't specify a source directory when using --system.
if not allow_global and ("PIP_SRC" not in os.environ):
src.extend(["--src", "{0}".format(project.virtualenv_src_location)])
@@ -1340,25 +1357,84 @@ def pip_install(
create_mirror_source(pypi_mirror) if is_pypi_url(source["url"]) else source
for source in sources
]
line_kwargs = {"as_list": True, "include_hashes": not ignore_hashes}
# Install dependencies when a package is a VCS dependency.
if requirement and requirement.vcs:
ignore_hashes = True
# Don't specify a source directory when using --system.
src_dir = None
if "PIP_SRC" in os.environ:
src_dir = os.environ["PIP_SRC"]
src = ["--src", os.environ["PIP_SRC"]]
if not requirement.editable:
no_deps = False
if src_dir is not None:
repo = requirement.req.get_vcs_repo(src_dir=src_dir)
else:
repo = requirement.req.get_vcs_repo()
write_to_tmpfile = True
line_kwargs["include_markers"] = False
line_kwargs["include_hashes"] = False
if not requirements_dir:
requirements_dir = vistir.path.create_tracked_tempdir(prefix="pipenv",
suffix="requirements")
f = vistir.compat.NamedTemporaryFile(
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir,
delete=False
)
line = "-e" if requirement.editable else ""
if requirement.editable or requirement.name is not None:
name = requirement.name
if requirement.extras:
name = "{0}{1}".format(name, requirement.extras_as_pip)
line = "-e {0}#egg={1}".format(vistir.path.path_to_url(repo.checkout_directory), requirement.name)
if repo.subdirectory:
line = "{0}&subdirectory={1}".format(line, repo.subdirectory)
else:
line = requirement.as_line(**line_kwargs)
f.write(vistir.misc.to_bytes(line))
r = f.name
f.close()
# Create files for hash mode.
if write_to_tmpfile and not r:
if not requirements_dir:
requirements_dir = vistir.path.create_tracked_tempdir(
prefix="pipenv", suffix="requirements")
f = vistir.compat.NamedTemporaryFile(
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir,
delete=False
)
ignore_hashes = True if not requirement.hashes else ignore_hashes
line = requirement.as_line(include_hashes=not ignore_hashes)
line = "{0} {1}".format(line, " ".join(src))
f.write(vistir.misc.to_bytes(line))
r = f.name
f.close()
if (requirement and requirement.editable) and not r:
line_kwargs = {"as_list": True}
if requirement.markers:
line_kwargs["include_markers"] = False
line_kwargs["include_markers"] = False
line_kwargs["include_hashes"] = False
install_reqs = requirement.as_line(**line_kwargs)
if requirement.editable and install_reqs[0].startswith("-e "):
req, install_reqs = install_reqs[0], install_reqs[1:]
possible_hashes = install_reqs[:]
editable_opt, req = req.split(" ", 1)
install_reqs = [editable_opt, req] + install_reqs
if not all(item.startswith("--hash") for item in install_reqs):
ignore_hashes = True
# hashes must be passed via a file
ignore_hashes = True
elif r:
install_reqs = ["-r", r]
with open(r) as f:
if "--hash" not in f.read():
ignore_hashes = True
else:
ignore_hashes = True if not requirement.hashes else False
install_reqs = requirement.as_line(as_list=True)
ignore_hashes = True
install_reqs = requirement.as_line(as_list=True, include_hashes=not ignore_hashes)
if not requirement.markers:
install_reqs = [escape_cmd(r) for r in install_reqs]
elif len(install_reqs) > 1:
@@ -1379,7 +1455,11 @@ def pip_install(
pip_command.extend(prepare_pip_source_args(sources))
if not ignore_hashes:
pip_command.append("--require-hashes")
if not use_pep517:
from .vendor.packaging.version import parse as parse_version
pip_command.append("--no-build-isolation")
if project.environment.pip_version >= parse_version("19.0"):
pip_command.append("--no-use-pep517")
if environments.is_verbose():
click.echo("$ {0}".format(pip_command), err=True)
cache_dir = vistir.compat.Path(PIPENV_CACHE_DIR)
@@ -1398,6 +1478,8 @@ def pip_install(
)
cmd = Script.parse(pip_command)
pip_command = cmd.cmdify()
c = None
# with project.environment.activated():
c = delegator.run(pip_command, block=block, env=pip_config)
return c
@@ -1856,7 +1938,7 @@ def do_install(
# This is for if the user passed in dependencies, then we want to make sure we
else:
from .vendor.requirementslib import Requirement
from .vendor.requirementslib.models.requirements import Requirement
# make a tuple of (display_name, entry)
pkg_list = packages + ["-e {0}".format(pkg) for pkg in editable_packages]
@@ -1907,6 +1989,15 @@ def do_install(
pypi_mirror=pypi_mirror,
)
if not c.ok:
sp.write_err(vistir.compat.fs_str(
"{0}: {1}".format(
crayons.red("WARNING"),
"Failed installing package {0}".format(pkg_line)
),
))
sp.write_err(vistir.compat.fs_str(
"Error text: {0}".format(c.out)
))
raise RuntimeError(c.err)
except (ValueError, RuntimeError) as e:
sp.write_err(vistir.compat.fs_str(
@@ -1954,12 +2045,20 @@ def do_install(
crayons.normal(fix_utf8(""), bold=True),
)
))
# Add the package to the Pipfile.
try:
project.add_package_to_pipfile(pkg_requirement, dev)
except ValueError as e:
import traceback
sp.write_err(
"{0} {1}".format(
crayons.red("Error:", bold=True), traceback.format_exc()
)
)
sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(
"Failed adding package to Pipfile"
))
sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Installation Succeeded"))
# Add the package to the Pipfile.
try:
project.add_package_to_pipfile(pkg_requirement, dev)
except ValueError as e:
raise exceptions.PipfileException(e)
# Update project settings with pre preference.
if pre:
project.update_settings({"allow_prereleases": pre})
@@ -2158,7 +2257,7 @@ def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
# otherwise its value will be changed
os.environ["PIPENV_ACTIVE"] = vistir.misc.fs_str("1")
os.environ.pop("PIP_SHIMS_BASE_MODULE", None)
if fancy:
@@ -2297,6 +2396,8 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None):
load_dot_env()
previous_pip_shims_module = os.environ.pop("PIP_SHIMS_BASE_MODULE", None)
# Activate virtualenv under the current interpreter's environment
inline_activate_virtual_environment()
@@ -2304,10 +2405,9 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None):
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
# such as in inline_activate_virtual_environment
# otherwise its value will be changed
previous_pipenv_active_value = os.environ.get("PIPENV_ACTIVE")
os.environ["PIPENV_ACTIVE"] = vistir.misc.fs_str("1")
os.environ.pop("PIP_SHIMS_BASE_MODULE", None)
try:
script = project.build_script(command, args)
cmd_string = ' '.join([script.command] + script.args)
@@ -2315,10 +2415,21 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None):
click.echo(crayons.normal("$ {0}".format(cmd_string)), err=True)
except ScriptEmptyError:
click.echo("Can't run script {0!r}-it's empty?", err=True)
run_args = [script]
run_kwargs = {}
if os.name == "nt":
do_run_nt(script)
run_fn = do_run_nt
else:
do_run_posix(script, command=command)
run_fn = do_run_posix
run_kwargs = {"command": command}
try:
run_fn(*run_args, **run_kwargs)
finally:
os.environ.pop("PIPENV_ACTIVE", None)
if previous_pipenv_active_value is not None:
os.environ["PIPENV_ACTIVE"] = previous_pipenv_active_value
if previous_pip_shims_module is not None:
os.environ["PIP_SHIMS_BASE_MODULE"] = previous_pip_shims_module
def do_check(
@@ -2607,34 +2718,32 @@ def do_sync(
click.echo(crayons.green("All dependencies are now up-to-date!"))
def do_clean(ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None):
def do_clean(
ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None,
system=False
):
# Ensure that virtualenv is available.
from packaging.utils import canonicalize_name
ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror)
ensure_lockfile(pypi_mirror=pypi_mirror)
# Make sure that the virtualenv's site packages are configured correctly
# otherwise we may end up removing from the global site packages directory
installed_package_names = [
canonicalize_name(pkg.project_name) for pkg
in project.environment.get_installed_packages()
]
installed_package_names = project.installed_package_names.copy()
# Remove known "bad packages" from the list.
for bad_package in BAD_PACKAGES:
if canonicalize_name(bad_package) in installed_package_names:
if environments.is_verbose():
click.echo("Ignoring {0}.".format(bad_package), err=True)
del installed_package_names[installed_package_names.index(
canonicalize_name(bad_package)
)]
installed_package_names.remove(canonicalize_name(bad_package))
# Intelligently detect if --dev should be used or not.
develop = [canonicalize_name(k) for k in project.lockfile_content["develop"].keys()]
default = [canonicalize_name(k) for k in project.lockfile_content["default"].keys()]
for used_package in set(develop + default):
locked_packages = {
canonicalize_name(pkg) for pkg in project.lockfile_package_names["combined"]
}
for used_package in locked_packages:
if used_package in installed_package_names:
del installed_package_names[installed_package_names.index(
canonicalize_name(used_package)
)]
installed_package_names.remove(used_package)
failure = False
cmd = [which_pip(allow_global=system), "uninstall", "-y", "-qq"]
for apparent_bad_package in installed_package_names:
if dry_run and not bare:
click.echo(apparent_bad_package)
@@ -2646,9 +2755,8 @@ def do_clean(ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirro
)
)
# Uninstall the package.
c = delegator.run(
"{0} uninstall {1} -y".format(which_pip(), apparent_bad_package)
)
cmd_str = Script.parse(cmd + [apparent_bad_package]).cmdify()
c = delegator.run(cmd_str, block=True)
if c.return_code != 0:
failure = True
sys.exit(int(failure))
+70 -24
View File
@@ -11,15 +11,16 @@ import sys
from distutils.sysconfig import get_python_lib
from sysconfig import get_paths
import itertools
import pkg_resources
import six
import vistir
import pipenv
from cached_property import cached_property
from .vendor.cached_property import cached_property
import vistir
from .utils import normalize_path
from .utils import normalize_path, make_posix
BASE_WORKING_SET = pkg_resources.WorkingSet(sys.path)
@@ -92,12 +93,16 @@ class Environment(object):
deps |= cls.resolve_dist(dist, working_set)
return deps
def add_dist(self, dist_name):
dist = pkg_resources.get_distribution(pkg_resources.Requirement(dist_name))
def extend_dists(self, dist):
extras = self.resolve_dist(dist, self.base_working_set)
self.extra_dists.append(dist)
if extras:
self.extra_dists.extend(extras)
def add_dist(self, dist_name):
dist = pkg_resources.get_distribution(pkg_resources.Requirement(dist_name))
self.extend_dists(dist)
@cached_property
def python_version(self):
with self.activated():
@@ -145,7 +150,7 @@ class Environment(object):
'stdlib': '/home/hawk/.pyenv/versions/3.7.1/lib/python3.7'}
"""
prefix = self.prefix.as_posix()
prefix = make_posix(self.prefix.as_posix())
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
paths = get_paths(install_scheme, vars={
'base': prefix,
@@ -154,8 +159,8 @@ class Environment(object):
paths["PATH"] = paths["scripts"] + os.pathsep + os.defpath
if "prefix" not in paths:
paths["prefix"] = prefix
purelib = get_python_lib(plat_specific=0, prefix=prefix)
platlib = get_python_lib(plat_specific=1, prefix=prefix)
purelib = make_posix(get_python_lib(plat_specific=0, prefix=prefix))
platlib = make_posix(get_python_lib(plat_specific=1, prefix=prefix))
if purelib == platlib:
lib_dirs = purelib
else:
@@ -163,7 +168,7 @@ class Environment(object):
paths["libdir"] = purelib
paths["purelib"] = purelib
paths["platlib"] = platlib
paths['PYTHONPATH'] = lib_dirs
paths['PYTHONPATH'] = os.pathsep.join(["", ".", lib_dirs])
paths["libdirs"] = lib_dirs
return paths
@@ -176,19 +181,21 @@ class Environment(object):
@property
def python(self):
"""Path to the environment python"""
py = vistir.compat.Path(self.base_paths["scripts"]).joinpath("python").as_posix()
py = vistir.compat.Path(self.base_paths["scripts"]).joinpath("python").absolute().as_posix()
if not py:
return vistir.compat.Path(sys.executable).as_posix()
return py
@cached_property
def sys_path(self):
"""The system path inside the environment
"""
The system path inside the environment
:return: The :data:`sys.path` from the environment
:rtype: list
"""
from .vendor.vistir.compat import JSONDecodeError
current_executable = vistir.compat.Path(sys.executable).as_posix()
if not self.python or self.python == current_executable:
return sys.path
@@ -196,12 +203,16 @@ class Environment(object):
return sys.path
cmd_args = [self.python, "-c", "import json, sys; print(json.dumps(sys.path))"]
path, _ = vistir.misc.run(cmd_args, return_object=False, nospin=True, block=True, combine_stderr=False, write_to_stdout=False)
path = json.loads(path.strip())
try:
path = json.loads(path.strip())
except JSONDecodeError:
path = sys.path
return path
@cached_property
def sys_prefix(self):
"""The prefix run inside the context of the environment
"""
The prefix run inside the context of the environment
:return: The python prefix inside the environment
:rtype: :data:`sys.prefix`
@@ -236,15 +247,33 @@ class Environment(object):
return "purelib", purelib
return "platlib", self.paths["platlib"]
@property
def pip_version(self):
"""
Get the pip version in the environment. Useful for knowing which args we can use
when installing.
"""
from .vendor.packaging.version import parse as parse_version
pip = next(iter(
pkg for pkg in self.get_installed_packages() if pkg.key == "pip"
), None)
if pip is not None:
pip_version = parse_version(pip.version)
return parse_version("18.0")
def get_distributions(self):
"""Retrives the distributions installed on the library path of the environment
"""
Retrives the distributions installed on the library path of the environment
:return: A set of distributions found on the library path
:rtype: iterator
"""
pkg_resources = self.safe_import("pkg_resources")
return pkg_resources.find_distributions(self.paths["PYTHONPATH"])
libdirs = self.base_paths["libdirs"].split(os.pathsep)
dists = (pkg_resources.find_distributions(libdir) for libdir in libdirs)
for dist in itertools.chain.from_iterable(dists):
yield dist
def find_egg(self, egg_dist):
"""Find an egg by name in the given environment"""
@@ -271,21 +300,28 @@ class Environment(object):
def dist_is_in_project(self, dist):
"""Determine whether the supplied distribution is in the environment."""
from .project import _normalized
prefix = _normalized(self.base_paths["prefix"])
prefixes = [
_normalized(prefix) for prefix in self.base_paths["libdirs"].split(os.pathsep)
if _normalized(prefix).startswith(_normalized(self.prefix.as_posix()))
]
location = self.locate_dist(dist)
if not location:
return False
return _normalized(location).startswith(prefix)
location = _normalized(make_posix(location))
return any(location.startswith(prefix) for prefix in prefixes)
def get_installed_packages(self):
"""Returns all of the installed packages in a given environment"""
workingset = self.get_working_set()
packages = [pkg for pkg in workingset if self.dist_is_in_project(pkg)]
packages = [
pkg for pkg in workingset
if self.dist_is_in_project(pkg) and pkg.key != "python"
]
return packages
@contextlib.contextmanager
def get_finder(self, pre=False):
from .vendor.pip_shims import Command, cmdoptions, index_group, PackageFinder
from .vendor.pip_shims.shims import Command, cmdoptions, index_group, PackageFinder
from .environments import PIPENV_CACHE_DIR
index_urls = [source.get("url") for source in self.sources]
@@ -475,6 +511,7 @@ class Environment(object):
vendor_dir = parent_path.joinpath("vendor").as_posix()
patched_dir = parent_path.joinpath("patched").as_posix()
parent_path = parent_path.as_posix()
self.add_dist("pip")
prefix = self.prefix.as_posix()
with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path():
os.environ["PATH"] = os.pathsep.join([
@@ -484,12 +521,24 @@ class Environment(object):
])
os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8")
os.environ["PYTHONDONTWRITEBYTECODE"] = vistir.compat.fs_str("1")
os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"]
from .environments import PIPENV_USE_SYSTEM
if self.is_venv:
os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"]
os.environ["VIRTUAL_ENV"] = vistir.compat.fs_str(prefix)
else:
if not PIPENV_USE_SYSTEM and not os.environ.get("VIRTUAL_ENV"):
os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"]
os.environ.pop("PYTHONHOME", None)
sys.path = self.sys_path
sys.prefix = self.sys_prefix
site.addsitedir(self.base_paths["purelib"])
pip = self.safe_import("pip")
pip_vendor = self.safe_import("pip._vendor")
pep517_dir = os.path.join(os.path.dirname(pip_vendor.__file__), "pep517")
site.addsitedir(pep517_dir)
os.environ["PYTHONPATH"] = os.pathsep.join([
os.environ.get("PYTHONPATH", self.base_paths["PYTHONPATH"]), pep517_dir
])
if include_extras:
site.addsitedir(parent_path)
sys.path.extend([parent_path, patched_dir, vendor_dir])
@@ -593,10 +642,7 @@ class Environment(object):
monkey_patch.activate()
pip_shims = self.safe_import("pip_shims")
pathset_base = pip_shims.UninstallPathSet
import recursive_monkey_patch
recursive_monkey_patch.monkey_patch(
PatchedUninstaller, pathset_base
)
pathset_base._permitted = PatchedUninstaller._permitted
dist = next(
iter(filter(lambda d: d.project_name == pkgname, self.get_working_set())),
None
+28 -4
View File
@@ -280,13 +280,37 @@ def is_quiet(threshold=-1):
def is_in_virtualenv():
pipenv_active = os.environ.get("PIPENV_ACTIVE")
virtual_env = os.environ.get("VIRTUAL_ENV")
return (PIPENV_USE_SYSTEM or virtual_env) and not (
pipenv_active or PIPENV_IGNORE_VIRTUALENVS
"""
Check virtualenv membership dynamically
:return: True or false depending on whether we are in a regular virtualenv or not
:rtype: bool
"""
pipenv_active = os.environ.get("PIPENV_ACTIVE", False)
virtual_env = None
use_system = False
ignore_virtualenvs = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS", False))
if not pipenv_active and not ignore_virtualenvs:
virtual_env = os.environ.get("VIRTUAL_ENV")
use_system = bool(virtual_env)
return (use_system or virtual_env) and not (
pipenv_active or ignore_virtualenvs
)
PIPENV_SPINNER_FAIL_TEXT = fix_utf8(u"{0}") if not PIPENV_HIDE_EMOJIS else ("{0}")
PIPENV_SPINNER_OK_TEXT = fix_utf8(u"{0}") if not PIPENV_HIDE_EMOJIS else ("{0}")
def is_type_checking():
try:
from typing import TYPE_CHECKING
except ImportError:
return False
return TYPE_CHECKING
MYPY_RUNNING = is_type_checking()
+15 -8
View File
@@ -4,7 +4,7 @@ import itertools
import sys
from pprint import pformat
from traceback import format_exception
from traceback import format_exception, format_tb
import six
@@ -25,8 +25,8 @@ def handle_exception(exc_type, exception, traceback, hook=sys.excepthook):
hook(exc_type, exception, traceback)
else:
exc = format_exception(exc_type, exception, traceback)
lines = itertools.chain.from_iterable([l.splitlines() for l in exc])
lines = list(lines)[-11:-1]
tb = format_tb(traceback, limit=-6)
lines = itertools.chain.from_iterable([frame.splitlines() for frame in tb])
for line in lines:
line = line.strip("'").strip('"').strip("\n").strip()
if not line.startswith("File"):
@@ -179,7 +179,8 @@ class SystemUsageError(PipenvOptionsError):
crayons.red("Warning", bold=True)
),
]
message = crayons.blue("See also: {0}".format(crayons.white("-deploy flag.")))
if message is None:
message = crayons.blue("See also: {0}".format(crayons.white("--deploy flag.")))
super(SystemUsageError, self).__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs)
@@ -235,11 +236,11 @@ class UninstallError(PipenvException):
crayons.yellow("$ {0}".format(command), bold=True)
)),]
extra.extend([crayons.blue(line.strip()) for line in return_values.splitlines()])
if isinstance(package, (tuple, list)):
if isinstance(package, (tuple, list, set)):
package = " ".join(package)
message = "{0} {1}...".format(
message = "{0!s} {1!s}...".format(
crayons.normal("Failed to uninstall package(s)"),
crayons.yellow(package, bold=True)
crayons.yellow(str(package), bold=True)
)
self.exit_code = return_code
PipenvException.__init__(self, message=fix_utf8(message), extra=extra)
@@ -248,8 +249,14 @@ class UninstallError(PipenvException):
class InstallError(PipenvException):
def __init__(self, package, **kwargs):
message = "{0} {1}".format(
package_message = ""
if package is not None:
package_message = crayons.normal("Couldn't install package {0}\n".format(
crayons.white(package, bold=True)
))
message = "{0} {1} {2}".format(
crayons.red("ERROR:", bold=True),
package_message,
crayons.yellow("Package installation failed...")
)
extra = kwargs.pop("extra", [])
+1 -1
View File
@@ -2,4 +2,4 @@
__author__ = """pyup.io"""
__email__ = 'support@pyup.io'
__version__ = '1.8.4'
__version__ = '1.8.5'
+68 -26
View File
@@ -6,17 +6,13 @@ from safety import __version__
from safety import safety
from safety.formatter import report
import itertools
from safety.util import read_requirements
from safety.util import read_requirements, read_vulnerabilities
from safety.errors import DatabaseFetchError, DatabaseFileNotFoundError, InvalidKeyError
try:
# pip 9
from pipenv.patched.notpip import get_installed_distributions
from json.decoder import JSONDecodeError
except ImportError:
# pip 10
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions
JSONDecodeError = ValueError
@click.group()
@click.version_option(version=__version__)
@@ -46,10 +42,17 @@ def cli():
help="Read input from one (or multiple) requirement files. Default: empty")
@click.option("ignore", "--ignore", "-i", multiple=True, type=str, default=[],
help="Ignore one (or multiple) vulnerabilities by ID. Default: empty")
def check(key, db, json, full_report, bare, stdin, files, cache, ignore):
@click.option("--output", "-o", default="",
help="Path to where output file will be placed. Default: empty")
@click.option("proxyhost", "--proxy-host", "-ph", multiple=False, type=str, default=None,
help="Proxy host IP or DNS --proxy-host")
@click.option("proxyport", "--proxy-port", "-pp", multiple=False, type=int, default=80,
help="Proxy port number --proxy-port")
@click.option("proxyprotocol", "--proxy-protocol", "-pr", multiple=False, type=str, default='http',
help="Proxy protocol (https or http) --proxy-protocol")
def check(key, db, json, full_report, bare, stdin, files, cache, ignore, output, proxyprotocol, proxyhost, proxyport):
if files and stdin:
click.secho("Can't read from --stdin and --file at the same time, exiting", fg="red")
click.secho("Can't read from --stdin and --file at the same time, exiting", fg="red", file=sys.stderr)
sys.exit(-1)
if files:
@@ -57,33 +60,72 @@ def check(key, db, json, full_report, bare, stdin, files, cache, ignore):
elif stdin:
packages = list(read_requirements(sys.stdin))
else:
packages = get_installed_distributions()
import pkg_resources
packages = [
d for d in pkg_resources.working_set
if d.key not in {"python", "wsgiref", "argparse"}
]
proxy_dictionary = {}
if proxyhost is not None:
if proxyprotocol in ["http", "https"]:
proxy_dictionary = {proxyprotocol: "{0}://{1}:{2}".format(proxyprotocol, proxyhost, str(proxyport))}
else:
click.secho("Proxy Protocol should be http or https only.", fg="red")
sys.exit(-1)
try:
vulns = safety.check(packages=packages, key=key, db_mirror=db, cached=cache, ignore_ids=ignore)
click.secho(report(
vulns=vulns,
full=full_report,
json_report=json,
bare_report=bare,
checked_packages=len(packages),
db=db,
key=key
)
)
vulns = safety.check(packages=packages, key=key, db_mirror=db, cached=cache, ignore_ids=ignore, proxy=proxy_dictionary)
output_report = report(vulns=vulns,
full=full_report,
json_report=json,
bare_report=bare,
checked_packages=len(packages),
db=db,
key=key)
if output:
with open(output, 'w+') as output_file:
output_file.write(output_report)
else:
click.secho(output_report, nl=False if bare and not vulns else True)
sys.exit(-1 if vulns else 0)
except InvalidKeyError:
click.secho("Your API Key '{key}' is invalid. See {link}".format(
key=key, link='https://goo.gl/O7Y1rS'),
fg="red")
fg="red",
file=sys.stderr)
sys.exit(-1)
except DatabaseFileNotFoundError:
click.secho("Unable to load vulnerability database from {db}".format(db=db), fg="red")
click.secho("Unable to load vulnerability database from {db}".format(db=db), fg="red", file=sys.stderr)
sys.exit(-1)
except DatabaseFetchError:
click.secho("Unable to load vulnerability database", fg="red")
click.secho("Unable to load vulnerability database", fg="red", file=sys.stderr)
sys.exit(-1)
@cli.command()
@click.option("--full-report/--short-report", default=False,
help='Full reports include a security advisory (if available). Default: '
'--short-report')
@click.option("--bare/--not-bare", default=False,
help='Output vulnerable packages only. Useful in combination with other tools.'
'Default: --not-bare')
@click.option("file", "--file", "-f", type=click.File(), required=True,
help="Read input from an insecure report file. Default: empty")
def review(full_report, bare, file):
if full_report and bare:
click.secho("Can't choose both --bare and --full-report/--short-report", fg="red")
sys.exit(-1)
try:
input_vulns = read_vulnerabilities(file)
except JSONDecodeError:
click.secho("Not a valid JSON file", fg="red")
sys.exit(-1)
vulns = safety.review(input_vulns)
output_report = report(vulns=vulns, full=full_report, bare_report=bare)
click.secho(output_report, nl=False if bare and not vulns else True)
if __name__ == "__main__":
cli()
+5 -3
View File
@@ -3,6 +3,7 @@ import platform
import sys
import json
import os
import textwrap
# python 2.7 compat
try:
@@ -110,9 +111,10 @@ class SheetReport(object):
descr = get_advisory(vuln)
for chunk in [descr[i:i + 76] for i in range(0, len(descr), 76)]:
for line in chunk.splitlines():
for pn, paragraph in enumerate(descr.replace('\r', '').split('\n\n')):
if pn:
table.append("{:76}".format(''))
for line in textwrap.wrap(paragraph, width=76):
try:
table.append("{:76}".format(line.encode('utf-8')))
except TypeError:
+23 -8
View File
@@ -9,6 +9,7 @@ import json
import time
import errno
class Vulnerability(namedtuple("Vulnerability",
["name", "spec", "version", "advisory", "vuln_id"])):
pass
@@ -64,7 +65,7 @@ def write_to_cache(db_name, data):
f.write(json.dumps(cache))
def fetch_database_url(mirror, db_name, key, cached):
def fetch_database_url(mirror, db_name, key, cached, proxy):
headers = {}
if key:
@@ -74,9 +75,8 @@ def fetch_database_url(mirror, db_name, key, cached):
cached_data = get_from_cache(db_name=db_name)
if cached_data:
return cached_data
url = mirror + db_name
r = requests.get(url=url, timeout=REQUEST_TIMEOUT, headers=headers)
r = requests.get(url=url, timeout=REQUEST_TIMEOUT, headers=headers, proxies=proxy)
if r.status_code == 200:
data = r.json()
if cached:
@@ -94,7 +94,7 @@ def fetch_database_file(path, db_name):
return json.loads(f.read())
def fetch_database(full=False, key=False, db=False, cached=False):
def fetch_database(full=False, key=False, db=False, cached=False, proxy={}):
if db:
mirrors = [db]
@@ -105,7 +105,7 @@ def fetch_database(full=False, key=False, db=False, cached=False):
for mirror in mirrors:
# mirror can either be a local path or a URL
if mirror.startswith("http://") or mirror.startswith("https://"):
data = fetch_database_url(mirror, db_name=db_name, key=key, cached=cached)
data = fetch_database_url(mirror, db_name=db_name, key=key, cached=cached, proxy=proxy)
else:
data = fetch_database_file(mirror, db_name=db_name)
if data:
@@ -120,10 +120,9 @@ def get_vulnerabilities(pkg, spec, db):
yield entry
def check(packages, key, db_mirror, cached, ignore_ids):
def check(packages, key, db_mirror, cached, ignore_ids, proxy):
key = key if key else os.environ.get("SAFETY_API_KEY", False)
db = fetch_database(key=key, db=db_mirror, cached=cached)
db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy)
db_full = None
vulnerable_packages = frozenset(db.keys())
vulnerable = []
@@ -152,3 +151,19 @@ def check(packages, key, db_mirror, cached, ignore_ids):
)
)
return vulnerable
def review(vulnerabilities):
vulnerable = []
for vuln in vulnerabilities:
current_vuln = {
"name": vuln[0],
"spec": vuln[1],
"version": vuln[2],
"advisory": vuln[3],
"vuln_id": vuln[4],
}
vulnerable.append(
Vulnerability(**current_vuln)
)
return vulnerable
+8 -1
View File
@@ -1,11 +1,17 @@
from dparse.parser import setuptools_parse_requirements_backport as _parse_requirements
from collections import namedtuple
import click
import sys
import json
import os
Package = namedtuple("Package", ["key", "version"])
RequirementFile = namedtuple("RequirementFile", ["path"])
def read_vulnerabilities(fh):
return json.load(fh)
def iter_lines(fh, lineno=0):
for line in fh.readlines()[lineno:]:
yield line
@@ -85,7 +91,8 @@ def read_requirements(fh, resolve=False):
"Warning: unpinned requirement '{req}' found in {fname}, "
"unable to check.".format(req=req.name,
fname=fname),
fg="yellow"
fg="yellow",
file=sys.stderr
)
except ValueError:
continue
+45 -28
View File
@@ -20,7 +20,7 @@ from first import first
import pipfile
import pipfile.api
from cached_property import cached_property
from .vendor.cached_property import cached_property
from .cmdparse import Script
from .environment import Environment
@@ -29,12 +29,13 @@ from .environments import (
PIPENV_PIPFILE, PIPENV_PYTHON, PIPENV_TEST_INDEX, PIPENV_VENV_IN_PROJECT,
is_in_virtualenv
)
from .vendor.requirementslib.models.utils import get_default_pyproject_backend
from .utils import (
cleanup_toml, convert_toml_outline_tables, find_requirements,
get_canonical_names, get_url_name, get_workon_home, is_editable,
is_installable_file, is_star, is_valid_url, is_virtual_environment,
looks_like_dir, normalize_drive, pep423_name, proper_case, python_version,
safe_expandvars
safe_expandvars, get_pipenv_dist
)
@@ -98,6 +99,9 @@ if PIPENV_PIPFILE:
else:
PIPENV_PIPFILE = _normalized(PIPENV_PIPFILE)
# Overwrite environment variable so that subprocesses can get the correct path.
# See https://github.com/pypa/pipenv/issues/3584
os.environ['PIPENV_PIPFILE'] = PIPENV_PIPFILE
# (path, file contents) => TOMLFile
# keeps track of pipfiles that we've seen so we do not need to re-parse 'em
_pipfile_cache = {}
@@ -340,7 +344,11 @@ class Project(object):
prefix=prefix, is_venv=is_venv, sources=sources, pipfile=self.parsed_pipfile,
project=self
)
self._environment.add_dist("pipenv")
pipenv_dist = get_pipenv_dist(pkg="pipenv")
if pipenv_dist:
self._environment.extend_dists(pipenv_dist)
else:
self._environment.add_dist("pipenv")
return self._environment
def get_outdated_packages(self):
@@ -525,18 +533,19 @@ class Project(object):
if not os.path.exists(self.path_to("setup.py")):
if not build_system or not build_system.get("requires"):
build_system = {
"requires": ["setuptools>=38.2.5", "wheel"],
"build-backend": "setuptools.build_meta",
"requires": ["setuptools>=40.8.0", "wheel"],
"build-backend": get_default_pyproject_backend(),
}
self._build_system = build_system
@property
def build_requires(self):
return self._build_system.get("requires", [])
return self._build_system.get("requires", ["setuptools>=40.8.0", "wheel"])
@property
def build_backend(self):
return self._build_system.get("build-backend", None)
return self._build_system.get("build-backend", get_default_pyproject_backend())
@property
def settings(self):
@@ -603,10 +612,8 @@ class Project(object):
def _get_editable_packages(self, dev=False):
section = "dev-packages" if dev else "packages"
# section = "{0}-editable".format(section)
packages = {
k: v
# for k, v in self._pipfile[section].items()
for k, v in self.parsed_pipfile.get(section, {}).items()
if is_editable(k) or is_editable(v)
}
@@ -615,10 +622,8 @@ class Project(object):
def _get_vcs_packages(self, dev=False):
from pipenv.vendor.requirementslib.utils import is_vcs
section = "dev-packages" if dev else "packages"
# section = "{0}-vcs".format(section)
packages = {
k: v
# for k, v in self._pipfile[section].items()
for k, v in self.parsed_pipfile.get(section, {}).items()
if is_vcs(v) or is_vcs(k)
}
@@ -681,7 +686,7 @@ class Project(object):
)
name = self.name if self.name is not None else "Pipfile"
config_parser = ConfigOptionParser(name=self.name)
config_parser = ConfigOptionParser(name=name)
config_parser.add_option_group(make_option_group(index_group, config_parser))
install = config_parser.option_groups[0]
indexes = (
@@ -856,7 +861,8 @@ class Project(object):
return self.pipfile_sources
def find_source(self, source):
"""given a source, find it.
"""
Given a source, find it.
source can be a url or an index name.
"""
@@ -869,23 +875,34 @@ class Project(object):
source = self.get_source(url=source)
return source
def get_source(self, name=None, url=None):
def get_source(self, name=None, url=None, refresh=False):
from .utils import is_url_equal
def find_source(sources, name=None, url=None):
source = None
if name:
source = [s for s in sources if s.get("name") == name]
source = next(iter(
s for s in sources if "name" in s and s["name"] == name
), None)
elif url:
source = [s for s in sources if url.startswith(s.get("url"))]
if source:
return first(source)
source = next(iter(
s for s in sources
if "url" in s and is_url_equal(url, s.get("url", ""))
), None)
if source is not None:
return source
found_source = find_source(self.sources, name=name, url=url)
if found_source:
return found_source
found_source = find_source(self.pipfile_sources, name=name, url=url)
if found_source:
return found_source
raise SourceNotFound(name or url)
sources = (self.sources, self.pipfile_sources)
if refresh:
self.clear_pipfile_cache()
sources = reversed(sources)
found = next(
iter(find_source(source, name=name, url=url) for source in sources), None
)
target = next(iter(t for t in (name, url) if t is not None))
if found is None:
raise SourceNotFound(target)
return found
def get_package_name_in_pipfile(self, package_name, dev=False):
"""Get the equivalent package name in pipfile"""
@@ -930,17 +947,17 @@ class Project(object):
# Don't re-capitalize file URLs or VCSs.
if not isinstance(package, Requirement):
package = Requirement.from_line(package.strip())
_, converted = package.pipfile_entry
req_name, converted = package.pipfile_entry
key = "dev-packages" if dev else "packages"
# Set empty group if it doesn't exist yet.
if key not in p:
p[key] = {}
name = self.get_package_name_in_pipfile(package.name, dev)
name = self.get_package_name_in_pipfile(req_name, dev)
if name and is_star(converted):
# Skip for wildcard version
return
# Add the package to the group.
p[key][name or pep423_name(package.name)] = converted
p[key][name or pep423_name(req_name)] = converted
# Write Pipfile.
self.write_toml(p)
+114 -41
View File
@@ -7,12 +7,48 @@ import sys
os.environ["PIP_PYTHON_PATH"] = str(sys.executable)
def _patch_path():
def find_site_path(pkg, site_dir=None):
import pkg_resources
if site_dir is not None:
site_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
working_set = pkg_resources.WorkingSet([site_dir] + sys.path[:])
for dist in working_set:
root = dist.location
base_name = dist.project_name if dist.project_name else dist.key
name = None
if "top_level.txt" in dist.metadata_listdir(""):
name = next(iter([l.strip() for l in dist.get_metadata_lines("top_level.txt") if l is not None]), None)
if name is None:
name = pkg_resources.safe_name(base_name).replace("-", "_")
if not any(pkg == _ for _ in [base_name, name]):
continue
path_options = [name, "{0}.py".format(name)]
path_options = [os.path.join(root, p) for p in path_options if p is not None]
path = next(iter(p for p in path_options if os.path.exists(p)), None)
if path is not None:
return (dist, path)
return (None, None)
def _patch_path(pipenv_site=None):
import site
pipenv_libdir = os.path.dirname(os.path.abspath(__file__))
pipenv_site_dir = os.path.dirname(pipenv_libdir)
site.addsitedir(pipenv_site_dir)
for _dir in ("vendor", "patched"):
pipenv_dist = None
if pipenv_site is not None:
pipenv_dist, pipenv_path = find_site_path("pipenv", site_dir=pipenv_site)
else:
pipenv_dist, pipenv_path = find_site_path("pipenv", site_dir=pipenv_site_dir)
if pipenv_dist is not None:
pipenv_dist.activate()
else:
site.addsitedir(next(iter(
sitedir for sitedir in (pipenv_site, pipenv_site_dir)
if sitedir is not None
), None))
if pipenv_path is not None:
pipenv_libdir = pipenv_path
for _dir in ("vendor", "patched", pipenv_libdir):
sys.path.insert(0, os.path.join(pipenv_libdir, _dir))
@@ -24,8 +60,11 @@ def get_parser():
parser.add_argument("--verbose", "-v", action="count", default=False)
parser.add_argument("--debug", action="store_true", default=False)
parser.add_argument("--system", action="store_true", default=False)
parser.add_argument("--parse-only", action="store_true", default=False)
parser.add_argument("--pipenv-site", metavar="pipenv_site_dir", action="store",
default=os.environ.get("PIPENV_SITE_DIR"))
parser.add_argument("--requirements-dir", metavar="requirements_dir", action="store",
default=os.environ.get("PIPENV_REQ_DIR"))
default=os.environ.get("PIPENV_REQ_DIR"))
parser.add_argument("packages", nargs="*")
return parser
@@ -41,22 +80,53 @@ def handle_parsed_args(parsed):
logging.getLogger("notpip").setLevel(logging.DEBUG)
elif parsed.verbose > 0:
logging.getLogger("notpip").setLevel(logging.INFO)
os.environ["PIPENV_VERBOSITY"] = str(parsed.verbose)
if "PIPENV_PACKAGES" in os.environ:
parsed.packages += os.environ.get("PIPENV_PACKAGES", "").strip().split("\n")
return parsed
def _main(pre, clear, verbose, system, requirements_dir, packages):
os.environ["PIP_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]])
os.environ["PIP_PYTHON_PATH"] = str(sys.executable)
def parse_packages(packages, pre, clear, system, requirements_dir=None):
from pipenv.vendor.requirementslib.models.requirements import Requirement
from pipenv.vendor.vistir.contextmanagers import cd, temp_path
from pipenv.utils import parse_indexes
parsed_packages = []
for package in packages:
indexes, trusted_hosts, line = parse_indexes(package)
line = " ".join(line)
pf = dict()
req = Requirement.from_line(line)
if not req.name:
with temp_path(), cd(req.req.setup_info.base_dir):
sys.path.insert(0, req.req.setup_info.base_dir)
req.req._setup_info.get_info()
req.update_name_from_path(req.req.setup_info.base_dir)
print(os.listdir(req.req.setup_info.base_dir))
try:
name, entry = req.pipfile_entry
except Exception:
continue
else:
if name is not None and entry is not None:
pf[name] = entry
parsed_packages.append(pf)
print("RESULTS:")
if parsed_packages:
print(json.dumps(parsed_packages))
else:
print(json.dumps([]))
def resolve_packages(pre, clear, verbose, system, requirements_dir, packages):
from pipenv.utils import create_mirror_source, resolve_deps, replace_pypi_sources
pypi_mirror_source = (
create_mirror_source(os.environ["PIPENV_PYPI_MIRROR"])
if "PIPENV_PYPI_MIRROR" in os.environ
else None
)
# os.environ["PIP_NO_BUILD_ISOLATION"] = "1"
# os.environ["PIP_NO_USE_PEP517"] = "1"
# os.environ["PIP_NO_DEPS"] = "1"
def resolve(packages, pre, project, sources, clear, system, requirements_dir=None):
return resolve_deps(
@@ -76,15 +146,8 @@ def _main(pre, clear, verbose, system, requirements_dir, packages):
if pypi_mirror_source
else project.pipfile_sources
)
results = resolve(
packages,
pre=pre,
project=project,
sources=sources,
clear=clear,
system=system,
requirements_dir=requirements_dir,
)
results = resolve(packages, pre=pre, project=project, sources=sources, clear=clear,
system=system, requirements_dir=requirements_dir)
print("RESULTS:")
if results:
print(json.dumps(results))
@@ -92,36 +155,46 @@ def _main(pre, clear, verbose, system, requirements_dir, packages):
print(json.dumps([]))
def main():
_patch_path()
import warnings
from pipenv.vendor.vistir.compat import ResourceWarning
warnings.simplefilter("ignore", category=ResourceWarning)
import io
import six
if six.PY3:
import atexit
stdout_wrapper = io.TextIOWrapper(sys.stdout.buffer, encoding='utf8')
atexit.register(stdout_wrapper.close)
stderr_wrapper = io.TextIOWrapper(sys.stderr.buffer, encoding='utf8')
atexit.register(stderr_wrapper.close)
sys.stdout = stdout_wrapper
sys.stderr = stderr_wrapper
def _main(pre, clear, verbose, system, requirements_dir, packages, parse_only=False):
os.environ["PIP_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]])
os.environ["PIP_PYTHON_PATH"] = str(sys.executable)
if parse_only:
parse_packages(
packages,
pre=pre,
clear=clear,
system=system,
requirements_dir=requirements_dir,
)
else:
from pipenv._compat import force_encoding
force_encoding()
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = str("1")
os.environ["PYTHONIOENCODING"] = str("utf-8")
resolve_packages(pre, clear, verbose, system, requirements_dir, packages)
def main():
parser = get_parser()
parsed, remaining = parser.parse_known_args()
# sys.argv = remaining
_patch_path(pipenv_site=parsed.pipenv_site)
import warnings
from pipenv.vendor.vistir.compat import ResourceWarning
from pipenv.vendor.vistir.misc import get_wrapped_stream
warnings.simplefilter("ignore", category=ResourceWarning)
import six
if six.PY3:
stdout = sys.stdout.buffer
stderr = sys.stderr.buffer
else:
stdout = sys.stdout
stderr = sys.stderr
sys.stderr = get_wrapped_stream(stderr)
sys.stdout = get_wrapped_stream(stdout)
from pipenv.vendor import colorama
colorama.init()
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = str("1")
os.environ["PYTHONIOENCODING"] = str("utf-8")
parsed = handle_parsed_args(parsed)
_main(parsed.pre, parsed.clear, parsed.verbose, parsed.system,
parsed.requirements_dir, parsed.packages)
parsed.requirements_dir, parsed.packages, parse_only=parsed.parse_only)
if __name__ == "__main__":
_patch_path()
from pipenv.vendor import colorama
colorama.init()
main()
+528 -214
View File
@@ -3,6 +3,7 @@ import contextlib
import errno
import logging
import os
import posixpath
import re
import shutil
import stat
@@ -17,15 +18,13 @@ import toml
import tomlkit
from click import echo as click_echo
from first import first
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc")) # noqa
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc")) # noqa
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc")) # noqa
from six.moves import Mapping, Sequence, Set
from six.moves.urllib.parse import urlparse
from urllib3 import util as urllib3_util
from vistir.compat import ResourceWarning
from vistir.misc import fs_str
from .vendor.vistir.compat import ResourceWarning, lru_cache
from .vendor.vistir.misc import fs_str
import crayons
import parse
@@ -33,6 +32,13 @@ import parse
from . import environments
from .exceptions import PipenvUsageError
from .pep508checker import lookup
from .vendor.urllib3 import util as urllib3_util
if environments.MYPY_RUNNING:
from typing import Tuple, Dict, Any, List, Union, Optional, Text
from .vendor.requirementslib.models.requirements import Requirement, Line
from .project import Project
logging.basicConfig(level=logging.ERROR)
@@ -41,7 +47,7 @@ specifiers = [k for k in lookup.keys()]
# List of version control systems we support.
VCS_LIST = ("git", "svn", "hg", "bzr")
SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://")
requests_session = None
requests_session = None # type: ignore
def _get_requests_session():
@@ -228,30 +234,16 @@ def prepare_pip_source_args(sources, pip_args=None):
return pip_args
def get_resolver_metadata(deps, index_lookup, markers_lookup, project, sources):
from .vendor.requirementslib.models.requirements import Requirement
constraints = []
for dep in deps:
if not dep:
continue
url = None
indexes, trusted_hosts, remainder = parse_indexes(dep)
if indexes:
url = indexes[0]
dep = " ".join(remainder)
req = Requirement.from_line(dep)
constraints.append(req.constraint_line)
if url:
source = first(
s for s in sources if s.get("url") and url.startswith(s["url"]))
if source:
index_lookup[req.name] = source.get("name")
# strip the marker and re-add it later after resolution
# but we will need a fallback in case resolution fails
# eg pypiwin32
if req.markers:
markers_lookup[req.name] = req.markers.replace('"', "'")
return constraints
@lru_cache()
def get_pipenv_sitedir():
# type: () -> Optional[str]
import pkg_resources
site_dir = next(
iter(d for d in pkg_resources.working_set if d.key.lower() == "pipenv"), None
)
if site_dir is not None:
return site_dir.location
return None
class Resolver(object):
@@ -286,8 +278,10 @@ class Resolver(object):
"sources={self.sources})>".format(self=self)
)
def _get_pip_command(self):
from pip_shims.shims import Command
@staticmethod
@lru_cache()
def _get_pip_command():
from .vendor.pip_shims.shims import Command
class PipCommand(Command):
"""Needed for pip-tools."""
@@ -297,6 +291,140 @@ class Resolver(object):
from pipenv.patched.piptools.scripts.compile import get_pip_command
return get_pip_command()
@classmethod
def get_metadata(
cls,
deps, # type: List[str]
index_lookup, # type: Dict[str, str]
markers_lookup, # type: Dict[str, str]
project, # type: Project
sources # type: Dict[str, str]
):
# type: (...) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]], Dict[str, str], Dict[str, str]]
constraints = set() # type: Set[str]
skipped = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]]
if index_lookup is None:
index_lookup = {}
if markers_lookup is None:
markers_lookup = {}
for dep in deps:
if not dep:
continue
req, req_idx, markers_idx = cls.parse_line(
dep, index_lookup=index_lookup, markers_lookup=markers_lookup, project=project
)
index_lookup.update(req_idx)
markers_lookup.update(markers_idx)
constraint_update, lockfile_update = cls.get_deps_from_req(req)
constraints |= constraint_update
skipped.update(lockfile_update)
return constraints, skipped, index_lookup, markers_lookup
@classmethod
def parse_line(
cls,
line, # type: str
index_lookup=None, # type: Dict[str, str]
markers_lookup=None, # type: Dict[str, str]
project=None # type: Optional[Project]
):
# type: (...) -> Tuple[Requirement, Dict[str, str], Dict[str, str]]
from .vendor.requirementslib.models.requirements import Requirement
from .exceptions import ResolutionFailure
if index_lookup is None:
index_lookup = {}
if markers_lookup is None:
markers_lookup = {}
if project is None:
from .project import Project
project = Project()
url = None
indexes, trusted_hosts, remainder = parse_indexes(line)
if indexes:
url = indexes[0]
line = " ".join(remainder)
try:
req = Requirement.from_line(line)
except ValueError:
raise ResolutionFailure("Failed to resolve requirement from line: {0!s}".format(line))
if url:
index_lookup[req.normalized_name] = project.get_source(
url=url, refresh=True).get("name")
# strip the marker and re-add it later after resolution
# but we will need a fallback in case resolution fails
# eg pypiwin32
if req.markers:
markers_lookup[req.normalized_name] = req.markers.replace('"', "'")
return req, index_lookup, markers_lookup
@classmethod
def get_deps_from_line(cls, line):
# type: (str) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]]
req, _, _ = cls.parse_line(line)
return cls.get_deps_from_req(req)
@classmethod
def get_deps_from_req(cls, req):
# type: (Requirement) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]]
from requirementslib.models.utils import _requirement_to_str_lowercase_name
constraints = set() # type: Set[str]
locked_deps = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]]
if req.is_file_or_url or req.is_vcs and not req.is_wheel:
# for local packages with setup.py files and potential direct url deps:
if req.is_vcs:
req_list, lockfile = get_vcs_deps(reqs=[req])
req = next(iter(req for req in req_list if req is not None), req_list)
entry = lockfile[pep423_name(req.normalized_name)]
else:
_, entry = req.pipfile_entry
parsed_line = req.req.parsed_line # type: Line
setup_info = None # type: Any
name = req.normalized_name
setup_info = req.req.setup_info
locked_deps[pep423_name(name)] = entry
requirements = [v for v in getattr(setup_info, "requires", {}).values()]
for r in requirements:
if getattr(r, "url", None) and not getattr(r, "editable", False):
if r is not None:
if not r.url:
continue
line = _requirement_to_str_lowercase_name(r)
new_req, _, _ = cls.parse_line(line)
if r.marker and not r.marker.evaluate():
new_constraints = {}
_, new_entry = req.pipfile_entry
new_lock = {
pep_423_name(new_req.normalized_name): new_entry
}
else:
new_constraints, new_lock = cls.get_deps_from_req(new_req)
locked_deps.update(new_lock)
constraints |= new_constraints
else:
if r is not None:
line = _requirement_to_str_lowercase_name(r)
constraints.add(line)
# ensure the top level entry remains as provided
# note that we shouldn't pin versions for editable vcs deps
if (not req.is_vcs or (req.is_vcs and not req.editable)):
if req.specifiers:
locked_deps[name]["version"] = req.specifiers
elif parsed_line.setup_info and parsed_line.setup_info.version:
locked_deps[name]["version"] = "=={}".format(
parsed_line.setup_info.version
)
# if not req.is_vcs:
locked_deps.update({name: entry})
if req.is_vcs and req.editable:
constraints.add(req.constraint_line)
if req.is_file_or_url and req.req.is_local and req.editable and (
req.req.setup_path is not None and os.path.exists(req.req.setup_path)):
constraints.add(req.constraint_line)
else:
constraints.add(req.constraint_line)
return constraints, locked_deps
return constraints, locked_deps
@property
def pip_command(self):
if self._pip_command is None:
@@ -418,6 +546,59 @@ class Resolver(object):
self.resolved_tree.update(results)
return self.resolved_tree
@classmethod
def prepend_hash_types(cls, checksums):
cleaned_checksums = []
for checksum in checksums:
if not checksum:
continue
if not checksum.startswith("sha256:"):
checksum = "sha256:{0}".format(checksum)
cleaned_checksums.append(checksum)
return cleaned_checksums
def collect_hashes(self, ireq):
from .vendor.requests import ConnectionError
collected_hashes = []
if ireq in self.hashes:
collected_hashes += list(self.hashes.get(ireq, []))
if self._should_include_hash(ireq):
try:
hash_map = self.get_hash(ireq)
collected_hashes += list(hash_map)
except (ValueError, KeyError, IndexError, ConnectionError):
pass
elif any(
"python.org" in source["url"] or "pypi.org" in source["url"]
for source in self.sources
):
pkg_url = "https://pypi.org/pypi/{0}/json".format(ireq.name)
session = _get_requests_session()
try:
# Grab the hashes from the new warehouse API.
r = session.get(pkg_url, timeout=10)
api_releases = r.json()["releases"]
cleaned_releases = {}
for api_version, api_info in api_releases.items():
api_version = clean_pkg_version(api_version)
cleaned_releases[api_version] = api_info
version = ""
if ireq.specifier:
spec = next(iter(s for s in list(ireq.specifier._specs)), None)
if spec:
version = spec.version
for release in cleaned_releases[version]:
collected_hashes.append(release["digests"]["sha256"])
collected_hashes = self.prepend_hash_types(collected_hashes)
except (ValueError, KeyError, ConnectionError):
if environments.is_verbose():
click_echo(
"{0}: Error generating hash for {1}".format(
crayons.red("Warning", bold=True), ireq.name
), err=True
)
return collected_hashes
@staticmethod
def _should_include_hash(ireq):
from pipenv.vendor.vistir.compat import Path, to_native_string
@@ -442,31 +623,43 @@ class Resolver(object):
return False
return True
def get_hash(self, ireq, ireq_hashes=None):
"""
Retrieve hashes for a specific ``InstallRequirement`` instance.
:param ireq: An ``InstallRequirement`` to retrieve hashes for
:type ireq: :class:`~pip_shims.InstallRequirement`
:return: A set of hashes.
:rtype: Set
"""
# We _ALWAYS MUST PRIORITIZE_ the inclusion of hashes from local sources
# PLEASE *DO NOT MODIFY THIS* TO CHECK WHETHER AN IREQ ALREADY HAS A HASH
# RESOLVED. The resolver will pull hashes from PyPI and only from PyPI.
# The entire purpose of this approach is to include missing hashes.
# This fixes a race condition in resolution for missing dependency caches
# see pypa/pipenv#3289
if self._should_include_hash(ireq) and (
not ireq_hashes or ireq.link.scheme == "file"
):
if not ireq_hashes:
ireq_hashes = set()
new_hashes = self.resolver.repository._hash_cache.get_hash(ireq.link)
ireq_hashes = add_to_set(ireq_hashes, new_hashes)
else:
ireq_hashes = set(ireq_hashes)
# The _ONLY CASE_ where we flat out set the value is if it isn't present
# It's a set, so otherwise we *always* need to do a union update
if ireq not in self.hashes:
return ireq_hashes
else:
return self.hashes[ireq] | ireq_hashes
def resolve_hashes(self):
if self.results is not None:
resolved_hashes = self.resolver.resolve_hashes(self.results)
for ireq, ireq_hashes in resolved_hashes.items():
# We _ALWAYS MUST PRIORITIZE_ the inclusion of hashes from local sources
# PLEASE *DO NOT MODIFY THIS* TO CHECK WHETHER AN IREQ ALREADY HAS A HASH
# RESOLVED. The resolver will pull hashes from PyPI and only from PyPI.
# The entire purpose of this approach is to include missing hashes.
# This fixes a race condition in resolution for missing dependency caches
# see pypa/pipenv#3289
if self._should_include_hash(ireq) and (
not ireq_hashes or ireq.link.scheme == "file"
):
if not ireq_hashes:
ireq_hashes = set()
new_hashes = self.resolver.repository._hash_cache.get_hash(ireq.link)
add_to_set(ireq_hashes, new_hashes)
else:
ireq_hashes = set(ireq_hashes)
# The _ONLY CASE_ where we flat out set the value is if it isn't present
# It's a set, so otherwise we *always* need to do a union update
if ireq not in self.hashes:
self.hashes[ireq] = ireq_hashes
else:
self.hashes[ireq] |= ireq_hashes
self.hashes[ireq] = self.get_hash(ireq, ireq_hashes=ireq_hashes)
return self.hashes
@@ -487,42 +680,106 @@ def actually_resolve_deps(
req_dir=None,
):
from pipenv.vendor.vistir.path import create_tracked_tempdir
from pipenv.vendor.requirementslib.models.requirements import Requirement
if not req_dir:
req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-")
warning_list = []
with warnings.catch_warnings(record=True) as warning_list:
constraints = get_resolver_metadata(
constraints, skipped, index_lookup, markers_lookup = Resolver.get_metadata(
deps, index_lookup, markers_lookup, project, sources,
)
resolver = Resolver(constraints, req_dir, project, sources, clear=clear, pre=pre)
resolved_tree = resolver.resolve()
hashes = resolver.resolve_hashes()
reqs = [(Requirement.from_ireq(ireq), ireq) for ireq in resolved_tree]
results = {}
for req, ireq in reqs:
if (req.vcs and req.editable and not req.is_direct_url):
continue
collected_hashes = resolver.collect_hashes(ireq)
if collected_hashes:
req = req.add_hashes(collected_hashes)
elif resolver._should_include_hash(ireq):
existing_hashes = hashes.get(ireq, set())
discovered_hashes = existing_hashes | resolver.get_hash(ireq)
if discovered_hashes:
req = req.add_hashes(discovered_hashes)
resolver.hashes[ireq] = discovered_hashes
if req.specifiers:
version = str(req.get_version())
else:
version = None
index = index_lookup.get(req.normalized_name)
markers = markers_lookup.get(req.normalized_name)
req.index = index
name, pf_entry = req.pipfile_entry
name = pep423_name(req.name)
entry = {}
if isinstance(pf_entry, six.string_types):
entry["version"] = pf_entry.lstrip("=")
else:
entry.update(pf_entry)
if version is not None:
entry["version"] = version
if req.line_instance.is_direct_url:
entry["file"] = req.req.uri
if collected_hashes:
entry["hashes"] = sorted(set(collected_hashes))
entry["name"] = name
if index: # and index != next(iter(project.sources), {}).get("name"):
entry.update({"index": index})
if markers:
entry.update({"markers": markers})
entry = translate_markers(entry)
if name in results:
results[name].update(entry)
else:
results[name] = entry
for k in list(skipped.keys()):
req = Requirement.from_pipfile(k, skipped[k])
ref = None
if req.is_vcs:
ref = req.commit_hash
ireq = req.as_ireq()
entry = skipped[k].copy()
entry["name"] = req.name
ref = ref if ref is not None else entry.get("ref")
if ref:
entry["ref"] = ref
if resolver._should_include_hash(ireq):
collected_hashes = resolver.collect_hashes(ireq)
if collected_hashes:
entry["hashes"] = sorted(set(collected_hashes))
if k in results:
results[k].update(entry)
else:
results[k] = entry
results = list(results.values())
for warning in warning_list:
_show_warning(warning.message, warning.category, warning.filename, warning.lineno,
warning.line)
return (resolved_tree, hashes, markers_lookup, resolver)
return (results, hashes, markers_lookup, resolver, skipped)
@contextlib.contextmanager
def create_spinner(text, nospin=None, spinner_name=None):
import vistir.spin
from .vendor.vistir import spin
from .vendor.vistir.misc import fs_str
if not spinner_name:
spinner_name = environments.PIPENV_SPINNER
if nospin is None:
nospin = environments.PIPENV_NOSPIN
with vistir.spin.create_spinner(
with spin.create_spinner(
spinner_name=spinner_name,
start_text=vistir.compat.fs_str(text),
start_text=fs_str(text),
nospin=nospin, write_to_stdout=False
) as sp:
yield sp
def resolve(cmd, sp):
from .vendor import delegator
import delegator
from .cmdparse import Script
from .vendor.pexpect.exceptions import EOF, TIMEOUT
from .vendor.vistir.compat import to_native_string
@@ -560,7 +817,7 @@ def resolve(cmd, sp):
return c
def get_locked_dep(dep, pipfile_section, prefer_pipfile=False):
def get_locked_dep(dep, pipfile_section, prefer_pipfile=True):
# the prefer pipfile flag is not used yet, but we are introducing
# it now for development purposes
# TODO: Is this implementation clear? How can it be improved?
@@ -570,8 +827,11 @@ def get_locked_dep(dep, pipfile_section, prefer_pipfile=False):
"pipfile_entry": None
}
if isinstance(dep, Mapping) and dep.get("name", ""):
name_options = [dep["name"], pep423_name(dep["name"])]
name = next(iter(k for k in name_options if k in pipfile_section), None)
dep_name = pep423_name(dep["name"])
name = next(iter(
k for k in pipfile_section.keys()
if pep423_name(k) == dep_name
), None)
entry = pipfile_section[name] if name else None
if entry:
@@ -581,24 +841,33 @@ def get_locked_dep(dep, pipfile_section, prefer_pipfile=False):
version = entry.get("version", "") if entry else ""
else:
version = entry if entry else ""
lockfile_version = lockfile_entry.get("version", "")
lockfile_name, lockfile_dict = lockfile_entry.copy().popitem()
lockfile_version = lockfile_dict.get("version", "")
# Keep pins from the lockfile
if prefer_pipfile and lockfile_version != version and version.startswith("=="):
lockfile_version = version
lockfile_dict["version"] = version
lockfile_entry[lockfile_name] = lockfile_dict
return lockfile_entry
def prepare_lockfile(results, pipfile, lockfile):
from .vendor.requirementslib.utils import is_vcs
# from .vendor.requirementslib.utils import is_vcs
for dep in results:
if not dep:
continue
# Merge in any relevant information from the pipfile entry, including
# markers, normalized names, URL info, etc that we may have dropped during lock
if not is_vcs(dep):
lockfile_entry = get_locked_dep(dep, pipfile)
name = next(iter(k for k in lockfile_entry.keys()))
current_entry = lockfile.get(name)
if not current_entry or not is_vcs(current_entry):
lockfile.update(lockfile_entry)
# if not is_vcs(dep):
lockfile_entry = get_locked_dep(dep, pipfile)
name = next(iter(k for k in lockfile_entry.keys()))
current_entry = lockfile.get(name)
if current_entry:
if not isinstance(current_entry, Mapping):
lockfile[name] = lockfile_entry[name]
else:
lockfile[name].update(lockfile_entry[name])
else:
lockfile[name] = lockfile_entry[name]
return lockfile
@@ -614,39 +883,52 @@ def venv_resolve_deps(
pipfile=None,
lockfile=None
):
"""
Resolve dependencies for a pipenv project, acts as a portal to the target environment.
Regardless of whether a virtual environment is present or not, this will spawn
a subproces which is isolated to the target environment and which will perform
dependency resolution. This function reads the output of that call and mutates
the provided lockfile accordingly, returning nothing.
:param List[:class:`~requirementslib.Requirement`] deps: A list of dependencies to resolve.
:param Callable which: [description]
:param project: The pipenv Project instance to use during resolution
:param Optional[bool] pre: Whether to resolve pre-release candidates, defaults to False
:param Optional[bool] clear: Whether to clear the cache during resolution, defaults to False
:param Optional[bool] allow_global: Whether to use *sys.executable* as the python binary, defaults to False
:param Optional[str] pypi_mirror: A URL to substitute any time *pypi.org* is encountered, defaults to None
:param Optional[bool] dev: Whether to target *dev-packages* or not, defaults to False
:param pipfile: A Pipfile section to operate on, defaults to None
:type pipfile: Optional[Dict[str, Union[str, Dict[str, bool, List[str]]]]]
:param Dict[str, Any] lockfile: A project lockfile to mutate, defaults to None
:raises RuntimeError: Raised on resolution failure
:return: Nothing
:rtype: None
"""
from .vendor.vistir.misc import fs_str
from .vendor.vistir.compat import Path, to_native_string, JSONDecodeError
from .vendor.vistir.compat import Path, JSONDecodeError
from .vendor.vistir.path import create_tracked_tempdir
from . import resolver
from ._compat import decode_for_output
import json
vcs_deps = []
vcs_lockfile = {}
results = []
pipfile_section = "dev_packages" if dev else "packages"
pipfile_section = "dev-packages" if dev else "packages"
lockfile_section = "develop" if dev else "default"
vcs_section = "vcs_{0}".format(pipfile_section)
vcs_deps = getattr(project, vcs_section, {})
if not deps and not vcs_deps:
return {}
if not deps:
if not project.pipfile_exists:
return None
deps = project.parsed_pipfile.get(pipfile_section, {})
if not deps:
return None
if not pipfile:
pipfile = getattr(project, pipfile_section, None)
pipfile = getattr(project, pipfile_section, {})
if not lockfile:
lockfile = project._lockfile
req_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements")
if vcs_deps:
with create_spinner(text=fs_str("Pinning VCS Packages...")) as sp:
vcs_reqs, vcs_lockfile = get_vcs_deps(
project,
which=which,
clear=clear,
pre=pre,
allow_global=allow_global,
dev=dev,
)
vcs_deps = [req.as_line() for req in vcs_reqs if req.editable]
lockfile[lockfile_section].update(vcs_lockfile)
cmd = [
which("python", allow_global=allow_global),
Path(resolver.__file__.rstrip("co")).as_posix()
@@ -657,49 +939,43 @@ def venv_resolve_deps(
cmd.append("--clear")
if allow_global:
cmd.append("--system")
if dev:
cmd.append("--dev")
with temp_environ():
os.environ = {fs_str(k): fs_str(val) for k, val in os.environ.items()}
os.environ["PIPENV_PACKAGES"] = str("\n".join(deps))
os.environ.update({fs_str(k): fs_str(val) for k, val in os.environ.items()})
if pypi_mirror:
os.environ["PIPENV_PYPI_MIRROR"] = str(pypi_mirror)
os.environ["PIPENV_VERBOSITY"] = str(environments.PIPENV_VERBOSITY)
os.environ["PIPENV_REQ_DIR"] = fs_str(req_dir)
os.environ["PIP_NO_INPUT"] = fs_str("1")
with create_spinner(text=fs_str("Locking...")) as sp:
os.environ["PIPENV_SITE_DIR"] = get_pipenv_sitedir()
with create_spinner(text=decode_for_output("Locking...")) as sp:
# This conversion is somewhat slow on local and file-type requirements since
# we now download those requirements / make temporary folders to perform
# dependency resolution on them, so we are including this step inside the
# spinner context manager for the UX improvement
sp.write(decode_for_output("Building requirements..."))
deps = convert_deps_to_pip(
deps, project, r=False, include_index=True
)
constraints = set(deps)
os.environ["PIPENV_PACKAGES"] = str("\n".join(constraints))
sp.write(decode_for_output("Resolving dependencies..."))
c = resolve(cmd, sp)
results = c.out
if vcs_deps:
with temp_environ():
os.environ["PIPENV_PACKAGES"] = str("\n".join(vcs_deps))
sp.text = to_native_string("Locking VCS Dependencies...")
vcs_c = resolve(cmd, sp)
vcs_results, vcs_err = vcs_c.out, vcs_c.err
else:
vcs_results, vcs_err = "", ""
results = c.out.strip()
sp.green.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!"))
outputs = [results, vcs_results]
if environments.is_verbose():
for output in outputs:
click_echo(output.split("RESULTS:")[0], err=True)
click_echo(results.split("RESULTS:")[1], err=True)
try:
results = json.loads(results.split("RESULTS:")[1].strip())
if vcs_results:
# For vcs dependencies, treat the initial pass at locking (i.e. checkout)
# as the pipfile entry because it gets us an actual ref to use
vcs_results = json.loads(vcs_results.split("RESULTS:")[1].strip())
vcs_lockfile = prepare_lockfile(vcs_results, vcs_lockfile.copy(), vcs_lockfile)
else:
vcs_results = []
except (IndexError, JSONDecodeError):
for out, err in [(c.out, c.err), (vcs_results, vcs_err)]:
click_echo(out.strip(), err=True)
click_echo(err.strip(), err=True)
click_echo(c.out.strip(), err=True)
click_echo(c.err.strip(), err=True)
raise RuntimeError("There was a problem with locking.")
lockfile[lockfile_section] = prepare_lockfile(results, pipfile, lockfile[lockfile_section])
for k, v in vcs_lockfile.items():
if k in getattr(project, vcs_section, {}) or k not in lockfile[lockfile_section]:
lockfile[lockfile_section][k].update(v)
if lockfile_section not in lockfile:
lockfile[lockfile_section] = {}
prepare_lockfile(results, pipfile, lockfile[lockfile_section])
def resolve_deps(
@@ -716,9 +992,6 @@ def resolve_deps(
"""Given a list of dependencies, return a resolved list of dependencies,
using pip-tools -- and their hashes, using the warehouse API / pip.
"""
from .vendor.requests.exceptions import ConnectionError
from .vendor.requirementslib.models.requirements import Requirement
index_lookup = {}
markers_lookup = {}
python_path = which("python", allow_global=allow_global)
@@ -735,7 +1008,7 @@ def resolve_deps(
req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-requirements")
with HackedPythonVersion(python_version=python, python_path=python_path):
try:
resolved_tree, hashes, markers_lookup, resolver = actually_resolve_deps(
resolved_tree, hashes, markers_lookup, resolver, skipped = actually_resolve_deps(
deps,
index_lookup,
markers_lookup,
@@ -757,7 +1030,7 @@ def resolve_deps(
try:
# Attempt to resolve again, with different Python version information,
# particularly for particularly particular packages.
resolved_tree, hashes, markers_lookup, resolver = actually_resolve_deps(
resolved_tree, hashes, markers_lookup, resolver, skipped = actually_resolve_deps(
deps,
index_lookup,
markers_lookup,
@@ -769,64 +1042,7 @@ def resolve_deps(
)
except RuntimeError:
sys.exit(1)
for result in resolved_tree:
if not result.editable:
req = Requirement.from_ireq(result)
name = pep423_name(req.name)
version = str(req.get_version())
index = index_lookup.get(result.name)
req.index = index
collected_hashes = []
if result in hashes:
collected_hashes = list(hashes.get(result))
elif any(
"python.org" in source["url"] or "pypi.org" in source["url"]
for source in sources
):
pkg_url = "https://pypi.org/pypi/{0}/json".format(name)
session = _get_requests_session()
try:
# Grab the hashes from the new warehouse API.
r = session.get(pkg_url, timeout=10)
api_releases = r.json()["releases"]
cleaned_releases = {}
for api_version, api_info in api_releases.items():
api_version = clean_pkg_version(api_version)
cleaned_releases[api_version] = api_info
for release in cleaned_releases[version]:
collected_hashes.append(release["digests"]["sha256"])
collected_hashes = ["sha256:" + s for s in collected_hashes]
except (ValueError, KeyError, ConnectionError):
if environments.is_verbose():
click_echo(
"{0}: Error generating hash for {1}".format(
crayons.red("Warning", bold=True), name
), err=True
)
# # Collect un-collectable hashes (should work with devpi).
# try:
# collected_hashes = collected_hashes + list(
# list(resolver.resolve_hashes([result]).items())[0][1]
# )
# except (ValueError, KeyError, ConnectionError, IndexError):
# if verbose:
# print('Error generating hash for {}'.format(name))
req.hashes = sorted(set(collected_hashes))
name, _entry = req.pipfile_entry
entry = {}
if isinstance(_entry, six.string_types):
entry["version"] = _entry.lstrip("=")
else:
entry.update(_entry)
entry["version"] = version
entry["name"] = name
# if index:
# d.update({"index": index})
if markers_lookup.get(result.name):
entry.update({"markers": markers_lookup.get(result.name)})
entry = translate_markers(entry)
results.append(entry)
return results
return resolved_tree
def is_star(val):
@@ -845,7 +1061,9 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=True):
dependencies = []
for dep_name, dep in deps.items():
indexes = project.pipfile_sources if hasattr(project, "pipfile_sources") else []
if project:
project.clear_pipfile_cache()
indexes = getattr(project, "pipfile_sources", []) if project is not None else []
new_dep = Requirement.from_pipfile(dep_name, dep)
if new_dep.index:
include_index = True
@@ -1287,7 +1505,7 @@ def handle_remove_readonly(func, path, exc):
warnings.warn(default_warning_message.format(path), ResourceWarning)
return
raise
raise exc
def escape_cmd(cmd):
@@ -1305,37 +1523,53 @@ def safe_expandvars(value):
def get_vcs_deps(
project,
which=None,
clear=False,
pre=False,
allow_global=False,
project=None,
dev=False,
pypi_mirror=None,
packages=None,
reqs=None
):
from .vendor.requirementslib.models.requirements import Requirement
section = "vcs_dev_packages" if dev else "vcs_packages"
reqs = []
if reqs is None:
reqs = []
lockfile = {}
try:
packages = getattr(project, section)
except AttributeError:
return [], []
for pkg_name, pkg_pipfile in packages.items():
requirement = Requirement.from_pipfile(pkg_name, pkg_pipfile)
if not reqs:
if not project and not packages:
raise ValueError(
"Must supply either a project or a pipfile section to lock vcs dependencies."
)
if not packages:
try:
packages = getattr(project, section)
except AttributeError:
return [], []
reqs = [Requirement.from_pipfile(name, entry) for name, entry in packages.items()]
result = []
for requirement in reqs:
name = requirement.normalized_name
commit_hash = None
if requirement.is_vcs:
try:
with locked_repository(requirement) as repo:
with temp_path(), locked_repository(requirement) as repo:
from pipenv.vendor.requirementslib.models.requirements import Requirement
# from distutils.sysconfig import get_python_lib
# sys.path = [repo.checkout_directory, "", ".", get_python_lib(plat_specific=0)]
commit_hash = repo.get_commit_hash()
name = requirement.normalized_name
version = requirement._specifiers = "=={0}".format(requirement.req.setup_info.version)
lockfile[name] = requirement.pipfile_entry[1]
lockfile[name]['ref'] = commit_hash
reqs.append(requirement)
result.append(requirement)
version = requirement.specifiers
if not version and requirement.specifiers:
version = requirement.specifiers
if version:
lockfile[name]['version'] = version
except OSError:
continue
return reqs, lockfile
return result, lockfile
def translate_markers(pipfile_entry):
@@ -1378,25 +1612,38 @@ def translate_markers(pipfile_entry):
def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None):
from .vendor.requirementslib.utils import is_vcs
name = pep423_name(dep["name"])
lockfile = {}
# We use this to determine if there are any markers on top level packages
# So we can make sure those win out during resolution if the packages reoccur
lockfile = {"version": "=={0}".format(dep["version"])}
for key in ["hashes", "index", "extras"]:
if "version" in dep:
version = "{0}".format(dep["version"])
if not version.startswith("=="):
version = "=={0}".format(version)
lockfile["version"] = version
if is_vcs(dep):
ref = dep.get("ref", None)
if ref is not None:
lockfile["ref"] = ref
vcs_type = next(iter(k for k in dep.keys() if k in VCS_LIST), None)
if vcs_type:
lockfile[vcs_type] = dep[vcs_type]
if "subdirectory" in dep:
lockfile["subdirectory"] = dep["subdirectory"]
for key in ["hashes", "index", "extras", "editable"]:
if key in dep:
lockfile[key] = dep[key]
# In case we lock a uri or a file when the user supplied a path
# remove the uri or file keys from the entry and keep the path
if pipfile_entry and any(k in pipfile_entry for k in ["file", "path"]):
fs_key = next((k for k in ["path", "file"] if k in pipfile_entry), None)
lockfile_key = next((k for k in ["uri", "file", "path"] if k in lockfile), None)
if fs_key != lockfile_key:
try:
del lockfile[lockfile_key]
except KeyError:
# pass when there is no lock file, usually because it's the first time
pass
lockfile[fs_key] = pipfile_entry[fs_key]
fs_key = next(iter(k for k in ["path", "file"] if k in dep), None)
pipfile_fs_key = None
if pipfile_entry:
pipfile_fs_key = next(iter(k for k in ["path", "file"] if k in pipfile_entry), None)
if fs_key and pipfile_fs_key and fs_key != pipfile_fs_key:
lockfile[pipfile_fs_key] = pipfile_entry[pipfile_fs_key]
elif fs_key is not None:
lockfile[fs_key] = dep[fs_key]
# If a package is **PRESENT** in the pipfile but has no markers, make sure we
# **NEVER** include markers in the lockfile
@@ -1536,3 +1783,70 @@ def add_to_set(original_set, element):
original_set |= set(element)
else:
original_set.add(element)
return original_set
def is_url_equal(url, other_url):
# type: (str, str) -> bool
"""
Compare two urls by scheme, host, and path, ignoring auth
:param str url: The initial URL to compare
:param str url: Second url to compare to the first
:return: Whether the URLs are equal without **auth**, **query**, and **fragment**
:rtype: bool
>>> is_url_equal("https://user:pass@mydomain.com/some/path?some_query",
"https://user2:pass2@mydomain.com/some/path")
True
>>> is_url_equal("https://user:pass@mydomain.com/some/path?some_query",
"https://mydomain.com/some?some_query")
False
"""
if not isinstance(url, six.string_types):
raise TypeError("Expected string for url, received {0!r}".format(url))
if not isinstance(other_url, six.string_types):
raise TypeError("Expected string for url, received {0!r}".format(other_url))
parsed_url = urllib3_util.parse_url(url)
parsed_other_url = urllib3_util.parse_url(other_url)
unparsed = parsed_url._replace(auth=None, query=None, fragment=None).url
unparsed_other = parsed_other_url._replace(auth=None, query=None, fragment=None).url
return unparsed == unparsed_other
@lru_cache()
def make_posix(path):
# type: (str) -> str
"""
Convert a path with possible windows-style separators to a posix-style path
(with **/** separators instead of **\\** separators).
:param Text path: A path to convert.
:return: A converted posix-style path
:rtype: Text
>>> make_posix("c:/users/user/venvs/some_venv\\Lib\\site-packages")
"c:/users/user/venvs/some_venv/Lib/site-packages"
>>> make_posix("c:\\users\\user\\venvs\\some_venv")
"c:/users/user/venvs/some_venv"
"""
if not isinstance(path, six.string_types):
raise TypeError("Expected a string for path, received {0!r}...".format(path))
starts_with_sep = path.startswith(os.path.sep)
separated = normalize_path(path).split(os.path.sep)
if isinstance(separated, (list, tuple)):
path = posixpath.join(*separated)
if starts_with_sep:
path = "/{0}".format(path)
return path
def get_pipenv_dist(pkg="pipenv", pipenv_site=None):
from .resolver import find_site_path
pipenv_libdir = os.path.dirname(os.path.abspath(__file__))
if pipenv_site is None:
pipenv_site = os.path.dirname(pipenv_libdir)
pipenv_dist, _ = find_site_path(pkg, site_dir=pipenv_site)
return pipenv_dist
+1 -1
View File
@@ -18,7 +18,7 @@ from ._make import (
)
__version__ = "18.2.0"
__version__ = "19.1.0"
__title__ = "attrs"
__description__ = "Classes Without Boilerplate"
+15 -12
View File
@@ -23,9 +23,9 @@ from . import validators as validators
_T = TypeVar("_T")
_C = TypeVar("_C", bound=type)
_ValidatorType = Callable[[Any, Attribute, _T], Any]
_ValidatorType = Callable[[Any, Attribute[_T], _T], Any]
_ConverterType = Callable[[Any], _T]
_FilterType = Callable[[Attribute, Any], bool]
_FilterType = Callable[[Attribute[_T], _T], bool]
# FIXME: in reality, if multiple validators are passed they must be in a list or tuple,
# but those are invariant and so would prevent subtypes of _ValidatorType from working
# when passed in a list or tuple.
@@ -57,10 +57,10 @@ class Attribute(Generic[_T]):
metadata: Dict[Any, Any]
type: Optional[Type[_T]]
kw_only: bool
def __lt__(self, x: Attribute) -> bool: ...
def __le__(self, x: Attribute) -> bool: ...
def __gt__(self, x: Attribute) -> bool: ...
def __ge__(self, x: Attribute) -> bool: ...
def __lt__(self, x: Attribute[_T]) -> bool: ...
def __le__(self, x: Attribute[_T]) -> bool: ...
def __gt__(self, x: Attribute[_T]) -> bool: ...
def __ge__(self, x: Attribute[_T]) -> bool: ...
# NOTE: We had several choices for the annotation to use for type arg:
# 1) Type[_T]
@@ -167,6 +167,7 @@ def attrs(
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
) -> _C: ...
@overload
def attrs(
@@ -184,14 +185,15 @@ def attrs(
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
) -> Callable[[_C], _C]: ...
# TODO: add support for returning NamedTuple from the mypy plugin
class _Fields(Tuple[Attribute, ...]):
def __getattr__(self, name: str) -> Attribute: ...
class _Fields(Tuple[Attribute[Any], ...]):
def __getattr__(self, name: str) -> Attribute[Any]: ...
def fields(cls: type) -> _Fields: ...
def fields_dict(cls: type) -> Dict[str, Attribute]: ...
def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
def validate(inst: Any) -> None: ...
# TODO: add support for returning a proper attrs class from the mypy plugin
@@ -212,6 +214,7 @@ def make_class(
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
) -> type: ...
# _funcs --
@@ -223,7 +226,7 @@ def make_class(
def asdict(
inst: Any,
recurse: bool = ...,
filter: Optional[_FilterType] = ...,
filter: Optional[_FilterType[Any]] = ...,
dict_factory: Type[Mapping[Any, Any]] = ...,
retain_collection_types: bool = ...,
) -> Dict[str, Any]: ...
@@ -232,8 +235,8 @@ def asdict(
def astuple(
inst: Any,
recurse: bool = ...,
filter: Optional[_FilterType] = ...,
tuple_factory: Type[Sequence] = ...,
filter: Optional[_FilterType[Any]] = ...,
tuple_factory: Type[Sequence[Any]] = ...,
retain_collection_types: bool = ...,
) -> Tuple[Any, ...]: ...
def has(cls: type) -> bool: ...
+21 -25
View File
@@ -20,6 +20,7 @@ else:
if PY2:
from UserDict import IterableUserDict
from collections import Mapping, Sequence # noqa
# We 'bundle' isclass instead of using inspect as importing inspect is
# fairly expensive (order of 10-15 ms for a modern machine in 2016)
@@ -89,8 +90,27 @@ if PY2:
res.data.update(d) # We blocked update, so we have to do it like this.
return res
def just_warn(*args, **kw): # pragma: nocover
"""
We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2.
"""
else:
else: # Python 3 and later.
from collections.abc import Mapping, Sequence # noqa
def just_warn(*args, **kw):
"""
We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2.
"""
warnings.warn(
"Missing ctypes. Some features like bare super() or accessing "
"__class__ will not work with slotted classes.",
RuntimeWarning,
stacklevel=2,
)
def isclass(klass):
return isinstance(klass, type)
@@ -113,30 +133,6 @@ def import_ctypes():
return ctypes
if not PY2:
def just_warn(*args, **kw):
"""
We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2.
"""
warnings.warn(
"Missing ctypes. Some features like bare super() or accessing "
"__class__ will not work with slots classes.",
RuntimeWarning,
stacklevel=2,
)
else:
def just_warn(*args, **kw): # pragma: nocover
"""
We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2.
"""
def make_set_closure_cell():
"""
Moved into a function for testability.
+79 -27
View File
@@ -409,12 +409,11 @@ def _transform_attrs(cls, these, auto_attribs, kw_only):
a.kw_only is False
):
had_default = True
if was_kw_only is True and a.kw_only is False:
if was_kw_only is True and a.kw_only is False and a.init is True:
raise ValueError(
"Non keyword-only attributes are not allowed after a "
"keyword-only attribute. Attribute in question: {a!r}".format(
a=a
)
"keyword-only attribute (unless they are init=False). "
"Attribute in question: {a!r}".format(a=a)
)
if was_kw_only is False and a.init is True and a.kw_only is True:
was_kw_only = True
@@ -454,6 +453,7 @@ class _ClassBuilder(object):
"_has_post_init",
"_delete_attribs",
"_base_attr_map",
"_is_exc",
)
def __init__(
@@ -466,6 +466,7 @@ class _ClassBuilder(object):
auto_attribs,
kw_only,
cache_hash,
is_exc,
):
attrs, base_attrs, base_map = _transform_attrs(
cls, these, auto_attribs, kw_only
@@ -483,6 +484,7 @@ class _ClassBuilder(object):
self._cache_hash = cache_hash
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
self._delete_attribs = not bool(these)
self._is_exc = is_exc
self._cls_dict["__attrs_attrs__"] = self._attrs
@@ -530,6 +532,26 @@ class _ClassBuilder(object):
for name, value in self._cls_dict.items():
setattr(cls, name, value)
# Attach __setstate__. This is necessary to clear the hash code
# cache on deserialization. See issue
# https://github.com/python-attrs/attrs/issues/482 .
# Note that this code only handles setstate for dict classes.
# For slotted classes, see similar code in _create_slots_class .
if self._cache_hash:
existing_set_state_method = getattr(cls, "__setstate__", None)
if existing_set_state_method:
raise NotImplementedError(
"Currently you cannot use hash caching if "
"you specify your own __setstate__ method."
"See https://github.com/python-attrs/attrs/issues/494 ."
)
def cache_hash_set_state(chss_self, _):
# clear hash code cache
setattr(chss_self, _hash_cache_field, None)
setattr(cls, "__setstate__", cache_hash_set_state)
return cls
def _create_slots_class(self):
@@ -582,6 +604,8 @@ class _ClassBuilder(object):
"""
return tuple(getattr(self, name) for name in state_attr_names)
hash_caching_enabled = self._cache_hash
def slots_setstate(self, state):
"""
Automatically created by attrs.
@@ -589,6 +613,13 @@ class _ClassBuilder(object):
__bound_setattr = _obj_setattr.__get__(self, Attribute)
for name, value in zip(state_attr_names, state):
__bound_setattr(name, value)
# Clearing the hash code cache on deserialization is needed
# because hash codes can change from run to run. See issue
# https://github.com/python-attrs/attrs/issues/482 .
# Note that this code only handles setstate for slotted classes.
# For dict classes, see similar code in _patch_original_class .
if hash_caching_enabled:
__bound_setattr(_hash_cache_field, None)
# slots and frozen require __getstate__/__setstate__ to work
cd["__getstate__"] = slots_getstate
@@ -660,6 +691,7 @@ class _ClassBuilder(object):
self._slots,
self._cache_hash,
self._base_attr_map,
self._is_exc,
)
)
@@ -710,6 +742,7 @@ def attrs(
auto_attribs=False,
kw_only=False,
cache_hash=False,
auto_exc=False,
):
r"""
A class decorator that adds `dunder
@@ -815,10 +848,23 @@ def attrs(
:param bool cache_hash: Ensure that the object's hash code is computed
only once and stored on the object. If this is set to ``True``,
hashing must be either explicitly or implicitly enabled for this
class. If the hash code is cached, then no attributes of this
class which participate in hash code computation may be mutated
after object creation.
class. If the hash code is cached, avoid any reassignments of
fields involved in hash code computation or mutations of the objects
those fields point to after object creation. If such changes occur,
the behavior of the object's hash code is undefined.
:param bool auto_exc: If the class subclasses :class:`BaseException`
(which implicitly includes any subclass of any exception), the
following happens to behave like a well-behaved Python exceptions
class:
- the values for *cmp* and *hash* are ignored and the instances compare
and hash by the instance's ids (N.B. ``attrs`` will *not* remove
existing implementations of ``__hash__`` or the equality methods. It
just won't add own ones.),
- all attributes that are either passed into ``__init__`` or have a
default value are additionally available as a tuple in the ``args``
attribute,
- the value of *str* is ignored leaving ``__str__`` to base classes.
.. versionadded:: 16.0.0 *slots*
.. versionadded:: 16.1.0 *frozen*
@@ -838,12 +884,16 @@ def attrs(
to each other.
.. versionadded:: 18.2.0 *kw_only*
.. versionadded:: 18.2.0 *cache_hash*
.. versionadded:: 19.1.0 *auto_exc*
"""
def wrap(cls):
if getattr(cls, "__class__", None) is None:
raise TypeError("attrs only works with new-style classes.")
is_exc = auto_exc is True and issubclass(cls, BaseException)
builder = _ClassBuilder(
cls,
these,
@@ -853,13 +903,14 @@ def attrs(
auto_attribs,
kw_only,
cache_hash,
is_exc,
)
if repr is True:
builder.add_repr(repr_ns)
if str is True:
builder.add_str()
if cmp is True:
if cmp is True and not is_exc:
builder.add_cmp()
if hash is not True and hash is not False and hash is not None:
@@ -874,7 +925,11 @@ def attrs(
" hashing must be either explicitly or implicitly "
"enabled."
)
elif hash is True or (hash is None and cmp is True and frozen is True):
elif (
hash is True
or (hash is None and cmp is True and frozen is True)
and is_exc is False
):
builder.add_hash()
else:
if cache_hash:
@@ -1213,7 +1268,9 @@ def _add_repr(cls, ns=None, attrs=None):
return cls
def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
def _make_init(
attrs, post_init, frozen, slots, cache_hash, base_attr_map, is_exc
):
attrs = [a for a in attrs if a.init or a.default is not NOTHING]
# We cache the generated init methods for the same kinds of attributes.
@@ -1222,16 +1279,18 @@ def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
unique_filename = "<attrs generated init {0}>".format(sha1.hexdigest())
script, globs, annotations = _attrs_to_init_script(
attrs, frozen, slots, post_init, cache_hash, base_attr_map
attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc
)
locs = {}
bytecode = compile(script, unique_filename, "exec")
attr_dict = dict((a.name, a) for a in attrs)
globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict})
if frozen is True:
# Save the lookup overhead in __init__ if we need to circumvent
# immutability.
globs["_cached_setattr"] = _obj_setattr
eval(bytecode, globs, locs)
# In order of debuggers like PDB being able to step through the code,
@@ -1245,24 +1304,10 @@ def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
__init__ = locs["__init__"]
__init__.__annotations__ = annotations
return __init__
def _add_init(cls, frozen):
"""
Add a __init__ method to *cls*. If *frozen* is True, make it immutable.
"""
cls.__init__ = _make_init(
cls.__attrs_attrs__,
getattr(cls, "__attrs_post_init__", False),
frozen,
_is_slot_cls(cls),
cache_hash=False,
base_attr_map={},
)
return cls
def fields(cls):
"""
Return the tuple of ``attrs`` attributes for a class.
@@ -1348,7 +1393,7 @@ def _is_slot_attr(a_name, base_attr_map):
def _attrs_to_init_script(
attrs, frozen, slots, post_init, cache_hash, base_attr_map
attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc
):
"""
Return a script of an initializer for *attrs* and a dict of globals.
@@ -1597,6 +1642,13 @@ def _attrs_to_init_script(
init_hash_cache = "self.%s = %s"
lines.append(init_hash_cache % (_hash_cache_field, "None"))
# For exceptions we rely on BaseException.__init__ for proper
# initialization.
if is_exc:
vals = ",".join("self." + a.name for a in attrs if a.init)
lines.append("BaseException.__init__(self, %s)" % (vals,))
args = ", ".join(args)
if kw_only_args:
if PY2:
+3 -3
View File
@@ -1,5 +1,5 @@
from typing import Union
from typing import Union, Any
from . import Attribute, _FilterType
def include(*what: Union[type, Attribute]) -> _FilterType: ...
def exclude(*what: Union[type, Attribute]) -> _FilterType: ...
def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
+113 -1
View File
@@ -136,7 +136,7 @@ class _InValidator(object):
def __call__(self, inst, attr, value):
try:
in_options = value in self.options
except TypeError as e: # e.g. `1 in "abc"`
except TypeError: # e.g. `1 in "abc"`
in_options = False
if not in_options:
@@ -168,3 +168,115 @@ def in_(options):
.. versionadded:: 17.1.0
"""
return _InValidator(options)
@attrs(repr=False, slots=False, hash=True)
class _IsCallableValidator(object):
def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if not callable(value):
raise TypeError("'{name}' must be callable".format(name=attr.name))
def __repr__(self):
return "<is_callable validator>"
def is_callable():
"""
A validator that raises a :class:`TypeError` if the initializer is called
with a value for this particular attribute that is not callable.
.. versionadded:: 19.1.0
:raises TypeError: With a human readable error message containing the
attribute (of type :class:`attr.Attribute`) name.
"""
return _IsCallableValidator()
@attrs(repr=False, slots=True, hash=True)
class _DeepIterable(object):
member_validator = attrib(validator=is_callable())
iterable_validator = attrib(
default=None, validator=optional(is_callable())
)
def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if self.iterable_validator is not None:
self.iterable_validator(inst, attr, value)
for member in value:
self.member_validator(inst, attr, member)
def __repr__(self):
iterable_identifier = (
""
if self.iterable_validator is None
else " {iterable!r}".format(iterable=self.iterable_validator)
)
return (
"<deep_iterable validator for{iterable_identifier}"
" iterables of {member!r}>"
).format(
iterable_identifier=iterable_identifier,
member=self.member_validator,
)
def deep_iterable(member_validator, iterable_validator=None):
"""
A validator that performs deep validation of an iterable.
:param member_validator: Validator to apply to iterable members
:param iterable_validator: Validator to apply to iterable itself
(optional)
.. versionadded:: 19.1.0
:raises TypeError: if any sub-validators fail
"""
return _DeepIterable(member_validator, iterable_validator)
@attrs(repr=False, slots=True, hash=True)
class _DeepMapping(object):
key_validator = attrib(validator=is_callable())
value_validator = attrib(validator=is_callable())
mapping_validator = attrib(default=None, validator=optional(is_callable()))
def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if self.mapping_validator is not None:
self.mapping_validator(inst, attr, value)
for key in value:
self.key_validator(inst, attr, key)
self.value_validator(inst, attr, value[key])
def __repr__(self):
return (
"<deep_mapping validator for objects mapping {key!r} to {value!r}>"
).format(key=self.key_validator, value=self.value_validator)
def deep_mapping(key_validator, value_validator, mapping_validator=None):
"""
A validator that performs deep validation of a dictionary.
:param key_validator: Validator to apply to dictionary keys
:param value_validator: Validator to apply to dictionary values
:param mapping_validator: Validator to apply to top-level mapping
attribute (optional)
.. versionadded:: 19.1.0
:raises TypeError: if any sub-validators fail
"""
return _DeepMapping(key_validator, value_validator, mapping_validator)
+10
View File
@@ -12,3 +12,13 @@ def optional(
) -> _ValidatorType[Optional[_T]]: ...
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
def deep_iterable(
member_validator: _ValidatorType[_T],
iterable_validator: Optional[_ValidatorType[_T]],
) -> _ValidatorType[_T]: ...
def deep_mapping(
key_validator: _ValidatorType[_T],
value_validator: _ValidatorType[_T],
mapping_validator: Optional[_ValidatorType[_T]],
) -> _ValidatorType[_T]: ...
def is_callable() -> _ValidatorType[_T]: ...
+1 -1
View File
@@ -2,7 +2,7 @@
__author__ = "Daniel Greenfeld"
__email__ = "pydanny@gmail.com"
__version__ = "1.4.3"
__version__ = "1.5.1"
__license__ = "BSD"
from time import time
+2 -2
View File
@@ -1,3 +1,3 @@
from .core import where, old_where
from .core import where
__version__ = "2018.10.15"
__version__ = "2018.11.29"
+242
View File
@@ -4268,3 +4268,245 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
# Subject: CN=GTS Root R1 O=Google Trust Services LLC
# Label: "GTS Root R1"
# Serial: 146587175971765017618439757810265552097
# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85
# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8
# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
# Subject: CN=GTS Root R2 O=Google Trust Services LLC
# Label: "GTS Root R2"
# Serial: 146587176055767053814479386953112547951
# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b
# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d
# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
# Subject: CN=GTS Root R3 O=Google Trust Services LLC
# Label: "GTS Root R3"
# Serial: 146587176140553309517047991083707763997
# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25
# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5
# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5
-----BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
# Subject: CN=GTS Root R4 O=Google Trust Services LLC
# Label: "GTS Root R4"
# Serial: 146587176229350439916519468929765261721
# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26
# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb
# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd
-----BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
-----END CERTIFICATE-----
# Issuer: CN=UCA Global G2 Root O=UniTrust
# Subject: CN=UCA Global G2 Root O=UniTrust
# Label: "UCA Global G2 Root"
# Serial: 124779693093741543919145257850076631279
# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
UB+K+wb1whnw0A==
-----END CERTIFICATE-----
# Issuer: CN=UCA Extended Validation Root O=UniTrust
# Subject: CN=UCA Extended Validation Root O=UniTrust
# Label: "UCA Extended Validation Root"
# Serial: 106100277556486529736699587978573607008
# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
-----END CERTIFICATE-----
# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Label: "Certigna Root CA"
# Serial: 269714418870597844693661054334862075617
# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----
-17
View File
@@ -8,14 +8,6 @@ certifi.py
This module returns the installation location of cacert.pem.
"""
import os
import warnings
class DeprecatedBundleWarning(DeprecationWarning):
"""
The weak security bundle is being deprecated. Please bother your service
provider to get them to stop using cross-signed roots.
"""
def where():
@@ -24,14 +16,5 @@ def where():
return os.path.join(f, 'cacert.pem')
def old_where():
warnings.warn(
"The weak security bundle has been removed. certifi.old_where() is now an alias "
"of certifi.where(). Please update your code to use certifi.where() instead. "
"certifi.old_where() will be removed in 2018.",
DeprecatedBundleWarning
)
return where()
if __name__ == '__main__':
print(where())
-1
View File
@@ -25,4 +25,3 @@ 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.
+1 -2
View File
@@ -3,5 +3,4 @@ from .initialise import init, deinit, reinit, colorama_text
from .ansi import Fore, Back, Style, Cursor
from .ansitowin32 import AnsiToWin32
__version__ = '0.3.9'
__version__ = '0.4.1'
+32 -11
View File
@@ -13,14 +13,6 @@ if windll is not None:
winterm = WinTerm()
def is_stream_closed(stream):
return not hasattr(stream, 'closed') or stream.closed
def is_a_tty(stream):
return hasattr(stream, 'isatty') and stream.isatty()
class StreamWrapper(object):
'''
Wraps a stream (such as stdout), acting as a transparent proxy for all
@@ -36,9 +28,38 @@ class StreamWrapper(object):
def __getattr__(self, name):
return getattr(self.__wrapped, name)
def __enter__(self, *args, **kwargs):
# special method lookup bypasses __getattr__/__getattribute__, see
# https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
# thus, contextlib magic methods are not proxied via __getattr__
return self.__wrapped.__enter__(*args, **kwargs)
def __exit__(self, *args, **kwargs):
return self.__wrapped.__exit__(*args, **kwargs)
def write(self, text):
self.__convertor.write(text)
def isatty(self):
stream = self.__wrapped
if 'PYCHARM_HOSTED' in os.environ:
if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__):
return True
try:
stream_isatty = stream.isatty
except AttributeError:
return False
else:
return stream_isatty()
@property
def closed(self):
stream = self.__wrapped
try:
return stream.closed
except AttributeError:
return True
class AnsiToWin32(object):
'''
@@ -68,12 +89,12 @@ class AnsiToWin32(object):
# should we strip ANSI sequences from our output?
if strip is None:
strip = conversion_supported or (not is_stream_closed(wrapped) and not is_a_tty(wrapped))
strip = conversion_supported or (not self.stream.closed and not self.stream.isatty())
self.strip = strip
# should we should convert ANSI sequences into win32 calls?
if convert is None:
convert = conversion_supported and not is_stream_closed(wrapped) and is_a_tty(wrapped)
convert = conversion_supported and not self.stream.closed and self.stream.isatty()
self.convert = convert
# dict of ansi codes to win32 functions and parameters
@@ -149,7 +170,7 @@ class AnsiToWin32(object):
def reset_all(self):
if self.convert:
self.call_win32('m', (0,))
elif not self.strip and not is_stream_closed(self.wrapped):
elif not self.strip and not self.stream.closed:
self.wrapped.write(Style.RESET_ALL)
-2
View File
@@ -78,5 +78,3 @@ def wrap_stream(stream, convert, strip, autoreset, wrap):
if wrapper.should_wrap():
stream = wrapper.stream
return stream
+7 -11
View File
@@ -89,11 +89,6 @@ else:
]
_SetConsoleTitleW.restype = wintypes.BOOL
handles = {
STDOUT: _GetStdHandle(STDOUT),
STDERR: _GetStdHandle(STDERR),
}
def _winapi_test(handle):
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
@@ -101,17 +96,18 @@ else:
return bool(success)
def winapi_test():
return any(_winapi_test(h) for h in handles.values())
return any(_winapi_test(h) for h in
(_GetStdHandle(STDOUT), _GetStdHandle(STDERR)))
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
handle, byref(csbi))
return csbi
def SetConsoleTextAttribute(stream_id, attrs):
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
return _SetConsoleTextAttribute(handle, attrs)
def SetConsoleCursorPosition(stream_id, position, adjust=True):
@@ -129,11 +125,11 @@ else:
adjusted_position.Y += sr.Top
adjusted_position.X += sr.Left
# Resume normal processing
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
return _SetConsoleCursorPosition(handle, adjusted_position)
def FillConsoleOutputCharacter(stream_id, char, length, start):
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
char = c_char(char.encode())
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
@@ -144,7 +140,7 @@ else:
def FillConsoleOutputAttribute(stream_id, attr, length, start):
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
handle = handles[stream_id]
handle = _GetStdHandle(stream_id)
attribute = wintypes.WORD(attr)
length = wintypes.DWORD(length)
num_written = wintypes.DWORD(0)
+9 -2
View File
@@ -44,6 +44,7 @@ class WinTerm(object):
def reset_all(self, on_stderr=None):
self.set_attrs(self._default)
self.set_console(attrs=self._default)
self._light = 0
def fore(self, fore=None, light=False, on_stderr=False):
if fore is None:
@@ -122,12 +123,15 @@ class WinTerm(object):
if mode == 0:
from_coord = csbi.dwCursorPosition
cells_to_erase = cells_in_screen - cells_before_cursor
if mode == 1:
elif mode == 1:
from_coord = win32.COORD(0, 0)
cells_to_erase = cells_before_cursor
elif mode == 2:
from_coord = win32.COORD(0, 0)
cells_to_erase = cells_in_screen
else:
# invalid mode
return
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
# now set the buffer's attributes accordingly
@@ -147,12 +151,15 @@ class WinTerm(object):
if mode == 0:
from_coord = csbi.dwCursorPosition
cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
if mode == 1:
elif mode == 1:
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
cells_to_erase = csbi.dwCursorPosition.X
elif mode == 2:
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
cells_to_erase = csbi.dwSize.X
else:
# invalid mode
return
# fill the entire screen with blanks
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
# now set the buffer's attributes accordingly
-5
View File
@@ -1,5 +0,0 @@
This work is licensed under the Creative Commons
Attribution-ShareAlike 2.5 International License. To view a copy of
this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or
send a letter to Creative Commons, PO Box 1866, Mountain View,
CA 94042, USA.
-4
View File
@@ -1,4 +0,0 @@
from .cursor import hide, show, HiddenCursor
__all__ = ["hide", "show", "HiddenCursor"]
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
## Author: James Spencer: http://stackoverflow.com/users/1375885/james-spencer
## Packager: Gijs TImmers: https://github.com/GijsTimmers
## Based on James Spencer's answer on StackOverflow:
## http://stackoverflow.com/questions/5174810/how-to-turn-off-blinking-cursor-in-command-window
## Licence: CC-BY-SA-2.5
## http://creativecommons.org/licenses/by-sa/2.5/
## This work is licensed under the Creative Commons
## Attribution-ShareAlike 2.5 International License. To view a copy of
## this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or
## send a letter to Creative Commons, PO Box 1866, Mountain View,
## CA 94042, USA.
import sys
import os
if os.name == 'nt':
import ctypes
class _CursorInfo(ctypes.Structure):
_fields_ = [("size", ctypes.c_int),
("visible", ctypes.c_byte)]
def hide(stream=sys.stdout):
if os.name == 'nt':
ci = _CursorInfo()
handle = ctypes.windll.kernel32.GetStdHandle(-11)
ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
ci.visible = False
ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
elif os.name == 'posix':
stream.write("\033[?25l")
stream.flush()
def show(stream=sys.stdout):
if os.name == 'nt':
ci = _CursorInfo()
handle = ctypes.windll.kernel32.GetStdHandle(-11)
ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
ci.visible = True
ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
elif os.name == 'posix':
stream.write("\033[?25h")
stream.flush()
class HiddenCursor(object):
def __init__(self, stream=sys.stdout):
self._stream = stream
def __enter__(self):
hide(stream=self._stream)
def __exit__(self, type, value, traceback):
show(stream=self._stream)
+5
View File
@@ -1,4 +1,9 @@
import sys
try:
from StringIO import StringIO # noqa
except ImportError:
from io import StringIO # noqa
PY2 = sys.version_info[0] == 2
WIN = sys.platform.startswith('win')
text_type = unicode if PY2 else str # noqa
+54
View File
@@ -0,0 +1,54 @@
import os
class UndefinedValueError(Exception):
pass
class Undefined(object):
"""Class to represent undefined type. """
pass
# Reference instance to represent undefined values
undefined = Undefined()
def _cast_boolean(value):
"""
Helper to convert config values to boolean as ConfigParser do.
"""
_BOOLEANS = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False, '': False}
value = str(value)
if value.lower() not in _BOOLEANS:
raise ValueError('Not a boolean: %s' % value)
return _BOOLEANS[value.lower()]
def getenv(option, default=undefined, cast=undefined):
"""
Return the value for option or default if defined.
"""
# We can't avoid __contains__ because value may be empty.
if option in os.environ:
value = os.environ[option]
else:
if isinstance(default, Undefined):
raise UndefinedValueError('{} not found. Declare it as envvar or define a default value.'.format(option))
value = default
if isinstance(cast, Undefined):
return value
if cast is bool:
value = _cast_boolean(value)
elif cast is list:
value = [x for x in value.split(',') if x]
else:
value = cast(value)
return value
+126 -88
View File
@@ -2,47 +2,90 @@
from __future__ import absolute_import, print_function, unicode_literals
import codecs
import fileinput
import io
import os
import re
import shutil
import sys
from subprocess import Popen, PIPE, STDOUT
from subprocess import Popen
import tempfile
import warnings
from collections import OrderedDict
from collections import OrderedDict, namedtuple
from contextlib import contextmanager
from .compat import StringIO
from .compat import StringIO, PY2, WIN, text_type
__escape_decoder = codecs.getdecoder('unicode_escape')
__posix_variable = re.compile('\$\{[^\}]*\}')
__posix_variable = re.compile(r'\$\{[^\}]*\}')
_binding = re.compile(
r"""
(
\s* # leading whitespace
(?:export{0}+)? # export
( '[^']+' # single-quoted key
| [^=\#\s]+ # or unquoted key
)?
(?:
(?:{0}*={0}*) # equal sign
( '(?:\\'|[^'])*' # single-quoted value
| "(?:\\"|[^"])*" # or double-quoted value
| [^\#\r\n]* # or unquoted value
)
)?
\s* # trailing whitespace
(?:\#[^\r\n]*)? # comment
(?:\r|\n|\r\n)? # newline
)
""".format(r'[^\S\r\n]'),
re.MULTILINE | re.VERBOSE,
)
_escape_sequence = re.compile(r"\\[\\'\"abfnrtv]")
def decode_escaped(escaped):
return __escape_decoder(escaped)[0]
Binding = namedtuple('Binding', 'key value original')
def parse_line(line):
line = line.strip()
def decode_escapes(string):
def decode_match(match):
return codecs.decode(match.group(0), 'unicode-escape')
# Ignore lines with `#` or which doesn't have `=` in it.
if not line or line.startswith('#') or '=' not in line:
return None, None
return _escape_sequence.sub(decode_match, string)
k, v = line.split('=', 1)
if k.startswith('export '):
(_, _, k) = k.partition('export ')
def is_surrounded_by(string, char):
return (
len(string) > 1
and string[0] == string[-1] == char
)
# Remove any leading and trailing spaces in key, value
k, v = k.strip(), v.strip()
if v:
v = v.encode('unicode-escape').decode('ascii')
quoted = v[0] == v[-1] in ['"', "'"]
if quoted:
v = decode_escaped(v[1:-1])
def parse_binding(string, position):
match = _binding.match(string, position)
(matched, key, value) = match.groups()
if key is None or value is None:
key = None
value = None
else:
value_quoted = is_surrounded_by(value, "'") or is_surrounded_by(value, '"')
if value_quoted:
value = decode_escapes(value[1:-1])
else:
value = value.strip()
return (Binding(key=key, value=value, original=matched), match.end())
return k, v
def parse_stream(stream):
string = stream.read()
position = 0
length = len(string)
while position < length:
(binding, position) = parse_binding(string, position)
yield binding
class DotEnv():
@@ -52,19 +95,17 @@ class DotEnv():
self._dict = None
self.verbose = verbose
@contextmanager
def _get_stream(self):
self._is_file = False
if isinstance(self.dotenv_path, StringIO):
return self.dotenv_path
if os.path.exists(self.dotenv_path):
self._is_file = True
return io.open(self.dotenv_path)
if self.verbose:
warnings.warn("File doesn't exist {}".format(self.dotenv_path))
return StringIO('')
yield self.dotenv_path
elif os.path.isfile(self.dotenv_path):
with io.open(self.dotenv_path) as stream:
yield stream
else:
if self.verbose:
warnings.warn("File doesn't exist {}".format(self.dotenv_path))
yield StringIO('')
def dict(self):
"""Return dotenv as dict"""
@@ -76,17 +117,10 @@ class DotEnv():
return self._dict
def parse(self):
f = self._get_stream()
for line in f:
key, value = parse_line(line)
if not key:
continue
yield key, value
if self._is_file:
f.close()
with self._get_stream() as stream:
for mapping in parse_stream(stream):
if mapping.key is not None and mapping.value is not None:
yield mapping.key, mapping.value
def set_as_environment_variables(self, override=False):
"""
@@ -95,13 +129,12 @@ class DotEnv():
for k, v in self.dict().items():
if k in os.environ and not override:
continue
# With Python 2 on Windows, ensuree environment variables are
# system strings to avoid "TypeError: environment can only contain
# strings" in Python's subprocess module.
if sys.version_info.major < 3 and sys.platform == 'win32':
from pipenv.utils import fs_str
k = fs_str(k)
v = fs_str(v)
# With Python2 on Windows, force environment variables to str to avoid
# "TypeError: environment can only contain strings" in Python's subprocess.py.
if PY2 and WIN:
if isinstance(k, text_type) or isinstance(v, text_type):
k = k.encode('ascii')
v = v.encode('ascii')
os.environ[k] = v
return True
@@ -127,6 +160,20 @@ def get_key(dotenv_path, key_to_get):
return DotEnv(dotenv_path, verbose=True).get(key_to_get)
@contextmanager
def rewrite(path):
try:
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as dest:
with io.open(path) as source:
yield (source, dest)
except BaseException:
if os.path.isfile(dest.name):
os.unlink(dest.name)
raise
else:
shutil.move(dest.name, path)
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
"""
Adds or Updates a key/value to the given .env
@@ -142,20 +189,19 @@ def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
if " " in value_to_set:
quote_mode = "always"
line_template = '{}="{}"' if quote_mode == "always" else '{}={}'
line_template = '{}="{}"\n' if quote_mode == "always" else '{}={}\n'
line_out = line_template.format(key_to_set, value_to_set)
replaced = False
for line in fileinput.input(dotenv_path, inplace=True):
k, v = parse_line(line)
if k == key_to_set:
replaced = True
line = line_out
print(line, end='')
if not replaced:
with io.open(dotenv_path, "a") as f:
f.write("{}\n".format(line_out))
with rewrite(dotenv_path) as (source, dest):
replaced = False
for mapping in parse_stream(source):
if mapping.key == key_to_set:
dest.write(line_out)
replaced = True
else:
dest.write(mapping.original)
if not replaced:
dest.write(line_out)
return True, key_to_set, value_to_set
@@ -167,18 +213,17 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
If the .env path given doesn't exist, fails
If the given key doesn't exist in the .env, fails
"""
removed = False
if not os.path.exists(dotenv_path):
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path)
return None, key_to_unset
for line in fileinput.input(dotenv_path, inplace=True):
k, v = parse_line(line)
if k == key_to_unset:
removed = True
line = ''
print(line, end='')
removed = False
with rewrite(dotenv_path) as (source, dest):
for mapping in parse_stream(source):
if mapping.key == key_to_unset:
removed = True
else:
dest.write(mapping.original)
if not removed:
warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path))
@@ -194,7 +239,7 @@ def resolve_nested_variables(values):
first search in environ, if not found,
then look into the dotenv variables
"""
ret = os.getenv(name, values.get(name, ""))
ret = os.getenv(name, new_values.get(name, ""))
return ret
def _re_sub_callback(match_object):
@@ -204,10 +249,12 @@ def resolve_nested_variables(values):
"""
return _replacement(match_object.group()[2:-1])
for k, v in values.items():
values[k] = __posix_variable.sub(_re_sub_callback, v)
new_values = {}
return values
for k, v in values.items():
new_values[k] = __posix_variable.sub(_re_sub_callback, v)
return new_values
def _walk_to_root(path):
@@ -248,7 +295,7 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
for dirname in _walk_to_root(path):
check_path = os.path.join(dirname, filename)
if os.path.exists(check_path):
if os.path.isfile(check_path):
return check_path
if raise_error_if_not_found:
@@ -292,19 +339,10 @@ def run_command(command, env):
cmd_env.update(env)
p = Popen(command,
stdin=PIPE,
stdout=PIPE,
stderr=STDOUT,
universal_newlines=True,
bufsize=0,
shell=False,
env=cmd_env)
try:
out, _ = p.communicate()
print(out)
except Exception:
warnings.warn('An error occured, running the command:')
out, _ = p.communicate()
warnings.warn(out)
_, _ = p.communicate()
return p.returncode
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.9.1"
__version__ = "0.10.1"
Vendored Executable → Regular
View File
Vendored Executable → Regular
View File
Vendored Executable → Regular
View File
Vendored Executable → Regular
+1 -4
View File
@@ -267,10 +267,7 @@ def alabel(label):
try:
label = label.encode('ascii')
try:
ulabel(label)
except IDNAError:
raise IDNAError('The label {0} is not a valid A-label'.format(label))
ulabel(label)
if not valid_label_length(label):
raise IDNAError('Label too long')
return label
Vendored Executable → Regular
+104 -18
View File
@@ -1,6 +1,6 @@
# This file is automatically generated by tools/idna-data
__version__ = "10.0.0"
__version__ = "11.0.0"
scripts = {
'Greek': (
0x37000000374,
@@ -49,7 +49,7 @@ scripts = {
0x30210000302a,
0x30380000303c,
0x340000004db6,
0x4e0000009feb,
0x4e0000009ff0,
0xf9000000fa6e,
0xfa700000fada,
0x200000002a6d7,
@@ -62,7 +62,7 @@ scripts = {
'Hebrew': (
0x591000005c8,
0x5d0000005eb,
0x5f0000005f5,
0x5ef000005f5,
0xfb1d0000fb37,
0xfb380000fb3d,
0xfb3e0000fb3f,
@@ -248,6 +248,7 @@ joining_types = {
0x6fb: 68,
0x6fc: 68,
0x6ff: 68,
0x70f: 84,
0x710: 82,
0x712: 68,
0x713: 68,
@@ -522,6 +523,7 @@ joining_types = {
0x1875: 68,
0x1876: 68,
0x1877: 68,
0x1878: 68,
0x1880: 85,
0x1881: 85,
0x1882: 85,
@@ -690,6 +692,70 @@ joining_types = {
0x10bad: 68,
0x10bae: 68,
0x10baf: 85,
0x10d00: 76,
0x10d01: 68,
0x10d02: 68,
0x10d03: 68,
0x10d04: 68,
0x10d05: 68,
0x10d06: 68,
0x10d07: 68,
0x10d08: 68,
0x10d09: 68,
0x10d0a: 68,
0x10d0b: 68,
0x10d0c: 68,
0x10d0d: 68,
0x10d0e: 68,
0x10d0f: 68,
0x10d10: 68,
0x10d11: 68,
0x10d12: 68,
0x10d13: 68,
0x10d14: 68,
0x10d15: 68,
0x10d16: 68,
0x10d17: 68,
0x10d18: 68,
0x10d19: 68,
0x10d1a: 68,
0x10d1b: 68,
0x10d1c: 68,
0x10d1d: 68,
0x10d1e: 68,
0x10d1f: 68,
0x10d20: 68,
0x10d21: 68,
0x10d22: 82,
0x10d23: 68,
0x10f30: 68,
0x10f31: 68,
0x10f32: 68,
0x10f33: 82,
0x10f34: 68,
0x10f35: 68,
0x10f36: 68,
0x10f37: 68,
0x10f38: 68,
0x10f39: 68,
0x10f3a: 68,
0x10f3b: 68,
0x10f3c: 68,
0x10f3d: 68,
0x10f3e: 68,
0x10f3f: 68,
0x10f40: 68,
0x10f41: 68,
0x10f42: 68,
0x10f43: 68,
0x10f44: 68,
0x10f45: 85,
0x10f51: 68,
0x10f52: 68,
0x10f53: 68,
0x10f54: 82,
0x110bd: 85,
0x110cd: 85,
0x1e900: 68,
0x1e901: 68,
0x1e902: 68,
@@ -1034,14 +1100,15 @@ codepoint_classes = {
0x52d0000052e,
0x52f00000530,
0x5590000055a,
0x56100000587,
0x56000000587,
0x58800000589,
0x591000005be,
0x5bf000005c0,
0x5c1000005c3,
0x5c4000005c6,
0x5c7000005c8,
0x5d0000005eb,
0x5f0000005f3,
0x5ef000005f3,
0x6100000061b,
0x62000000640,
0x64100000660,
@@ -1054,12 +1121,13 @@ codepoint_classes = {
0x7100000074b,
0x74d000007b2,
0x7c0000007f6,
0x7fd000007fe,
0x8000000082e,
0x8400000085c,
0x8600000086b,
0x8a0000008b5,
0x8b6000008be,
0x8d4000008e2,
0x8d3000008e2,
0x8e300000958,
0x96000000964,
0x96600000970,
@@ -1077,6 +1145,7 @@ codepoint_classes = {
0x9e0000009e4,
0x9e6000009f2,
0x9fc000009fd,
0x9fe000009ff,
0xa0100000a04,
0xa0500000a0b,
0xa0f00000a11,
@@ -1136,8 +1205,7 @@ codepoint_classes = {
0xbd000000bd1,
0xbd700000bd8,
0xbe600000bf0,
0xc0000000c04,
0xc0500000c0d,
0xc0000000c0d,
0xc0e00000c11,
0xc1200000c29,
0xc2a00000c3a,
@@ -1276,7 +1344,7 @@ codepoint_classes = {
0x17dc000017de,
0x17e0000017ea,
0x18100000181a,
0x182000001878,
0x182000001879,
0x1880000018ab,
0x18b0000018f6,
0x19000000191f,
@@ -1544,11 +1612,11 @@ codepoint_classes = {
0x309d0000309f,
0x30a1000030fb,
0x30fc000030ff,
0x31050000312f,
0x310500003130,
0x31a0000031bb,
0x31f000003200,
0x340000004db6,
0x4e0000009feb,
0x4e0000009ff0,
0xa0000000a48d,
0xa4d00000a4fe,
0xa5000000a60d,
@@ -1655,8 +1723,10 @@ codepoint_classes = {
0xa7a50000a7a6,
0xa7a70000a7a8,
0xa7a90000a7aa,
0xa7af0000a7b0,
0xa7b50000a7b6,
0xa7b70000a7b8,
0xa7b90000a7ba,
0xa7f70000a7f8,
0xa7fa0000a828,
0xa8400000a874,
@@ -1664,8 +1734,7 @@ codepoint_classes = {
0xa8d00000a8da,
0xa8e00000a8f8,
0xa8fb0000a8fc,
0xa8fd0000a8fe,
0xa9000000a92e,
0xa8fd0000a92e,
0xa9300000a954,
0xa9800000a9c1,
0xa9cf0000a9da,
@@ -1743,7 +1812,7 @@ codepoint_classes = {
0x10a0500010a07,
0x10a0c00010a14,
0x10a1500010a18,
0x10a1900010a34,
0x10a1900010a36,
0x10a3800010a3b,
0x10a3f00010a40,
0x10a6000010a7d,
@@ -1756,6 +1825,11 @@ codepoint_classes = {
0x10b8000010b92,
0x10c0000010c49,
0x10cc000010cf3,
0x10d0000010d28,
0x10d3000010d3a,
0x10f0000010f1d,
0x10f2700010f28,
0x10f3000010f51,
0x1100000011047,
0x1106600011070,
0x1107f000110bb,
@@ -1763,10 +1837,11 @@ codepoint_classes = {
0x110f0000110fa,
0x1110000011135,
0x1113600011140,
0x1114400011147,
0x1115000011174,
0x1117600011177,
0x11180000111c5,
0x111ca000111cd,
0x111c9000111cd,
0x111d0000111db,
0x111dc000111dd,
0x1120000011212,
@@ -1786,7 +1861,7 @@ codepoint_classes = {
0x1132a00011331,
0x1133200011334,
0x113350001133a,
0x1133c00011345,
0x1133b00011345,
0x1134700011349,
0x1134b0001134e,
0x1135000011351,
@@ -1796,6 +1871,7 @@ codepoint_classes = {
0x1137000011375,
0x114000001144b,
0x114500001145a,
0x1145e0001145f,
0x11480000114c6,
0x114c7000114c8,
0x114d0000114da,
@@ -1807,15 +1883,17 @@ codepoint_classes = {
0x116500001165a,
0x11680000116b8,
0x116c0000116ca,
0x117000001171a,
0x117000001171b,
0x1171d0001172c,
0x117300001173a,
0x118000001183b,
0x118c0000118ea,
0x118ff00011900,
0x11a0000011a3f,
0x11a4700011a48,
0x11a5000011a84,
0x11a8600011a9a,
0x11a9d00011a9e,
0x11ac000011af9,
0x11c0000011c09,
0x11c0a00011c37,
@@ -1831,6 +1909,13 @@ codepoint_classes = {
0x11d3c00011d3e,
0x11d3f00011d48,
0x11d5000011d5a,
0x11d6000011d66,
0x11d6700011d69,
0x11d6a00011d8f,
0x11d9000011d92,
0x11d9300011d99,
0x11da000011daa,
0x11ee000011ef7,
0x120000001239a,
0x1248000012544,
0x130000001342f,
@@ -1845,11 +1930,12 @@ codepoint_classes = {
0x16b5000016b5a,
0x16b6300016b78,
0x16b7d00016b90,
0x16e6000016e80,
0x16f0000016f45,
0x16f5000016f7f,
0x16f8f00016fa0,
0x16fe000016fe2,
0x17000000187ed,
0x17000000187f2,
0x1880000018af3,
0x1b0000001b11f,
0x1b1700001b2fc,
Vendored Executable → Regular
View File
Vendored Executable → Regular
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = '2.7'
__version__ = '2.8'
Vendored Executable → Regular
+343 -317
View File
File diff suppressed because it is too large Load Diff
-33
View File
@@ -1,33 +0,0 @@
Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS
for more details.
Some rights reserved.
Redistribution and use in source and binary forms of the software as well
as documentation, with or without modification, are permitted provided
that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+28
View File
@@ -0,0 +1,28 @@
Copyright 2010 Pallets
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.
3. Neither the name of the copyright holder 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.
+140 -118
View File
@@ -1,75 +1,74 @@
# -*- coding: utf-8 -*-
"""
markupsafe
~~~~~~~~~~
markupsafe
~~~~~~~~~~
Implements a Markup string.
Implements an escape function and a Markup string to replace HTML
special characters with safe representations.
:copyright: (c) 2010 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import re
import string
from collections import Mapping
from markupsafe._compat import text_type, string_types, int_types, \
unichr, iteritems, PY2
__version__ = "1.0"
from ._compat import int_types
from ._compat import iteritems
from ._compat import Mapping
from ._compat import PY2
from ._compat import string_types
from ._compat import text_type
from ._compat import unichr
__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
__version__ = "1.1.1"
__all__ = ["Markup", "soft_unicode", "escape", "escape_silent"]
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
_entity_re = re.compile(r'&([^& ;]+);')
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
_entity_re = re.compile(r"&([^& ;]+);")
class Markup(text_type):
r"""Marks a string as being safe for inclusion in HTML/XML output without
needing to be escaped. This implements the `__html__` interface a couple
of frameworks and web applications use. :class:`Markup` is a direct
subclass of `unicode` and provides all the methods of `unicode` just that
it escapes arguments passed and always returns `Markup`.
"""A string that is ready to be safely inserted into an HTML or XML
document, either because it was escaped or because it was marked
safe.
The `escape` function returns markup objects so that double escaping can't
happen.
Passing an object to the constructor converts it to text and wraps
it to mark it safe without escaping. To escape the text, use the
:meth:`escape` class method instead.
The constructor of the :class:`Markup` class can be used for three
different things: When passed an unicode object it's assumed to be safe,
when passed an object with an HTML representation (has an `__html__`
method) that representation is used, otherwise the object passed is
converted into a unicode string and then assumed to be safe:
>>> Markup('Hello, <em>World</em>!')
Markup('Hello, <em>World</em>!')
>>> Markup(42)
Markup('42')
>>> Markup.escape('Hello, <em>World</em>!')
Markup('Hello &lt;em&gt;World&lt;/em&gt;!')
>>> Markup("Hello <em>World</em>!")
Markup(u'Hello <em>World</em>!')
>>> class Foo(object):
... def __html__(self):
... return '<a href="#">foo</a>'
This implements the ``__html__()`` interface that some frameworks
use. Passing an object that implements ``__html__()`` will wrap the
output of that method, marking it safe.
>>> class Foo:
... def __html__(self):
... return '<a href="/foo">foo</a>'
...
>>> Markup(Foo())
Markup(u'<a href="#">foo</a>')
Markup('<a href="/foo">foo</a>')
If you want object passed being always treated as unsafe you can use the
:meth:`escape` classmethod to create a :class:`Markup` object:
This is a subclass of the text type (``str`` in Python 3,
``unicode`` in Python 2). It has the same methods as that type, but
all methods escape their arguments and return a ``Markup`` instance.
>>> Markup.escape("Hello <em>World</em>!")
Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
Operations on a markup string are markup aware which means that all
arguments are passed through the :func:`escape` function:
>>> em = Markup("<em>%s</em>")
>>> em % "foo & bar"
Markup(u'<em>foo &amp; bar</em>')
>>> strong = Markup("<strong>%(text)s</strong>")
>>> strong % {'text': '<blink>hacker here</blink>'}
Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
>>> Markup("<em>Hello</em> ") + "<foo>"
Markup(u'<em>Hello</em> &lt;foo&gt;')
>>> Markup('<em>%s</em>') % 'foo & bar'
Markup('<em>foo &amp; bar</em>')
>>> Markup('<em>Hello</em> ') + '<foo>'
Markup('<em>Hello</em> &lt;foo&gt;')
"""
__slots__ = ()
def __new__(cls, base=u'', encoding=None, errors='strict'):
if hasattr(base, '__html__'):
def __new__(cls, base=u"", encoding=None, errors="strict"):
if hasattr(base, "__html__"):
base = base.__html__()
if encoding is None:
return text_type.__new__(cls, base)
@@ -79,12 +78,12 @@ class Markup(text_type):
return self
def __add__(self, other):
if isinstance(other, string_types) or hasattr(other, '__html__'):
if isinstance(other, string_types) or hasattr(other, "__html__"):
return self.__class__(super(Markup, self).__add__(self.escape(other)))
return NotImplemented
def __radd__(self, other):
if hasattr(other, '__html__') or isinstance(other, string_types):
if hasattr(other, "__html__") or isinstance(other, string_types):
return self.escape(other).__add__(self)
return NotImplemented
@@ -92,6 +91,7 @@ class Markup(text_type):
if isinstance(num, int_types):
return self.__class__(text_type.__mul__(self, num))
return NotImplemented
__rmul__ = __mul__
def __mod__(self, arg):
@@ -102,115 +102,124 @@ class Markup(text_type):
return self.__class__(text_type.__mod__(self, arg))
def __repr__(self):
return '%s(%s)' % (
self.__class__.__name__,
text_type.__repr__(self)
)
return "%s(%s)" % (self.__class__.__name__, text_type.__repr__(self))
def join(self, seq):
return self.__class__(text_type.join(self, map(self.escape, seq)))
join.__doc__ = text_type.join.__doc__
def split(self, *args, **kwargs):
return list(map(self.__class__, text_type.split(self, *args, **kwargs)))
split.__doc__ = text_type.split.__doc__
def rsplit(self, *args, **kwargs):
return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs)))
rsplit.__doc__ = text_type.rsplit.__doc__
def splitlines(self, *args, **kwargs):
return list(map(self.__class__, text_type.splitlines(
self, *args, **kwargs)))
return list(map(self.__class__, text_type.splitlines(self, *args, **kwargs)))
splitlines.__doc__ = text_type.splitlines.__doc__
def unescape(self):
r"""Unescape markup again into an text_type string. This also resolves
known HTML4 and XHTML entities:
"""Convert escaped markup back into a text string. This replaces
HTML entities with the characters they represent.
>>> Markup("Main &raquo; <em>About</em>").unescape()
u'Main \xbb <em>About</em>'
>>> Markup('Main &raquo; <em>About</em>').unescape()
'Main » <em>About</em>'
"""
from markupsafe._constants import HTML_ENTITIES
from ._constants import HTML_ENTITIES
def handle_match(m):
name = m.group(1)
if name in HTML_ENTITIES:
return unichr(HTML_ENTITIES[name])
try:
if name[:2] in ('#x', '#X'):
if name[:2] in ("#x", "#X"):
return unichr(int(name[2:], 16))
elif name.startswith('#'):
elif name.startswith("#"):
return unichr(int(name[1:]))
except ValueError:
pass
# Don't modify unexpected input.
return m.group()
return _entity_re.sub(handle_match, text_type(self))
def striptags(self):
r"""Unescape markup into an text_type string and strip all tags. This
also resolves known HTML4 and XHTML entities. Whitespace is
normalized to one:
""":meth:`unescape` the markup, remove tags, and normalize
whitespace to single spaces.
>>> Markup("Main &raquo; <em>About</em>").striptags()
u'Main \xbb About'
>>> Markup('Main &raquo;\t<em>About</em>').striptags()
'Main » About'
"""
stripped = u' '.join(_striptags_re.sub('', self).split())
stripped = u" ".join(_striptags_re.sub("", self).split())
return Markup(stripped).unescape()
@classmethod
def escape(cls, s):
"""Escape the string. Works like :func:`escape` with the difference
that for subclasses of :class:`Markup` this function would return the
correct subclass.
"""Escape a string. Calls :func:`escape` and ensures that for
subclasses the correct type is returned.
"""
rv = escape(s)
if rv.__class__ is not cls:
return cls(rv)
return rv
def make_simple_escaping_wrapper(name):
def make_simple_escaping_wrapper(name): # noqa: B902
orig = getattr(text_type, name)
def func(self, *args, **kwargs):
args = _escape_argspec(list(args), enumerate(args), self.escape)
_escape_argspec(kwargs, iteritems(kwargs), self.escape)
return self.__class__(orig(self, *args, **kwargs))
func.__name__ = orig.__name__
func.__doc__ = orig.__doc__
return func
for method in '__getitem__', 'capitalize', \
'title', 'lower', 'upper', 'replace', 'ljust', \
'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
'translate', 'expandtabs', 'swapcase', 'zfill':
for method in (
"__getitem__",
"capitalize",
"title",
"lower",
"upper",
"replace",
"ljust",
"rjust",
"lstrip",
"rstrip",
"center",
"strip",
"translate",
"expandtabs",
"swapcase",
"zfill",
):
locals()[method] = make_simple_escaping_wrapper(method)
# new in python 2.5
if hasattr(text_type, 'partition'):
def partition(self, sep):
return tuple(map(self.__class__,
text_type.partition(self, self.escape(sep))))
def rpartition(self, sep):
return tuple(map(self.__class__,
text_type.rpartition(self, self.escape(sep))))
def partition(self, sep):
return tuple(map(self.__class__, text_type.partition(self, self.escape(sep))))
# new in python 2.6
if hasattr(text_type, 'format'):
def format(*args, **kwargs):
self, args = args[0], args[1:]
formatter = EscapeFormatter(self.escape)
kwargs = _MagicFormatMapping(args, kwargs)
return self.__class__(formatter.vformat(self, args, kwargs))
def rpartition(self, sep):
return tuple(map(self.__class__, text_type.rpartition(self, self.escape(sep))))
def __html_format__(self, format_spec):
if format_spec:
raise ValueError('Unsupported format specification '
'for Markup.')
return self
def format(self, *args, **kwargs):
formatter = EscapeFormatter(self.escape)
kwargs = _MagicFormatMapping(args, kwargs)
return self.__class__(formatter.vformat(self, args, kwargs))
def __html_format__(self, format_spec):
if format_spec:
raise ValueError("Unsupported format specification " "for Markup.")
return self
# not in python 3
if hasattr(text_type, '__getslice__'):
__getslice__ = make_simple_escaping_wrapper('__getslice__')
if hasattr(text_type, "__getslice__"):
__getslice__ = make_simple_escaping_wrapper("__getslice__")
del method, make_simple_escaping_wrapper
@@ -229,7 +238,7 @@ class _MagicFormatMapping(Mapping):
self._last_index = 0
def __getitem__(self, key):
if key == '':
if key == "":
idx = self._last_index
self._last_index += 1
try:
@@ -246,35 +255,37 @@ class _MagicFormatMapping(Mapping):
return len(self._kwargs)
if hasattr(text_type, 'format'):
class EscapeFormatter(string.Formatter):
if hasattr(text_type, "format"):
class EscapeFormatter(string.Formatter):
def __init__(self, escape):
self.escape = escape
def format_field(self, value, format_spec):
if hasattr(value, '__html_format__'):
if hasattr(value, "__html_format__"):
rv = value.__html_format__(format_spec)
elif hasattr(value, '__html__'):
elif hasattr(value, "__html__"):
if format_spec:
raise ValueError('No format specification allowed '
'when formatting an object with '
'its __html__ method.')
raise ValueError(
"Format specifier {0} given, but {1} does not"
" define __html_format__. A class that defines"
" __html__ must define __html_format__ to work"
" with format specifiers.".format(format_spec, type(value))
)
rv = value.__html__()
else:
# We need to make sure the format spec is unicode here as
# otherwise the wrong callback methods are invoked. For
# instance a byte string there would invoke __str__ and
# not __unicode__.
rv = string.Formatter.format_field(
self, value, text_type(format_spec))
rv = string.Formatter.format_field(self, value, text_type(format_spec))
return text_type(self.escape(rv))
def _escape_argspec(obj, iterable, escape):
"""Helper for various string-wrapped functions."""
for key, value in iterable:
if hasattr(value, '__html__') or isinstance(value, string_types):
if hasattr(value, "__html__") or isinstance(value, string_types):
obj[key] = escape(value)
return obj
@@ -286,20 +297,31 @@ class _MarkupEscapeHelper(object):
self.obj = obj
self.escape = escape
__getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x], s.escape)
__unicode__ = __str__ = lambda s: text_type(s.escape(s.obj))
__repr__ = lambda s: str(s.escape(repr(s.obj)))
__int__ = lambda s: int(s.obj)
__float__ = lambda s: float(s.obj)
def __getitem__(self, item):
return _MarkupEscapeHelper(self.obj[item], self.escape)
def __str__(self):
return text_type(self.escape(self.obj))
__unicode__ = __str__
def __repr__(self):
return str(self.escape(repr(self.obj)))
def __int__(self):
return int(self.obj)
def __float__(self):
return float(self.obj)
# we have to import it down here as the speedups and native
# modules imports the markup type which is define above.
try:
from markupsafe._speedups import escape, escape_silent, soft_unicode
from ._speedups import escape, escape_silent, soft_unicode
except ImportError:
from markupsafe._native import escape, escape_silent, soft_unicode
from ._native import escape, escape_silent, soft_unicode
if not PY2:
soft_str = soft_unicode
__all__.append('soft_str')
__all__.append("soft_str")
+15 -8
View File
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-
"""
markupsafe._compat
~~~~~~~~~~~~~~~~~~
markupsafe._compat
~~~~~~~~~~~~~~~~~~
Compatibility module for different Python versions.
:copyright: (c) 2013 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
import sys
@@ -17,10 +15,19 @@ if not PY2:
string_types = (str,)
unichr = chr
int_types = (int,)
iteritems = lambda x: iter(x.items())
def iteritems(x):
return iter(x.items())
from collections.abc import Mapping
else:
text_type = unicode
string_types = (str, unicode)
unichr = unichr
int_types = (int, long)
iteritems = lambda x: x.iteritems()
def iteritems(x):
return x.iteritems()
from collections import Mapping
+257 -260
View File
@@ -1,267 +1,264 @@
# -*- coding: utf-8 -*-
"""
markupsafe._constants
~~~~~~~~~~~~~~~~~~~~~
markupsafe._constants
~~~~~~~~~~~~~~~~~~~~~
Highlevel implementation of the Markup string.
:copyright: (c) 2010 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
HTML_ENTITIES = {
'AElig': 198,
'Aacute': 193,
'Acirc': 194,
'Agrave': 192,
'Alpha': 913,
'Aring': 197,
'Atilde': 195,
'Auml': 196,
'Beta': 914,
'Ccedil': 199,
'Chi': 935,
'Dagger': 8225,
'Delta': 916,
'ETH': 208,
'Eacute': 201,
'Ecirc': 202,
'Egrave': 200,
'Epsilon': 917,
'Eta': 919,
'Euml': 203,
'Gamma': 915,
'Iacute': 205,
'Icirc': 206,
'Igrave': 204,
'Iota': 921,
'Iuml': 207,
'Kappa': 922,
'Lambda': 923,
'Mu': 924,
'Ntilde': 209,
'Nu': 925,
'OElig': 338,
'Oacute': 211,
'Ocirc': 212,
'Ograve': 210,
'Omega': 937,
'Omicron': 927,
'Oslash': 216,
'Otilde': 213,
'Ouml': 214,
'Phi': 934,
'Pi': 928,
'Prime': 8243,
'Psi': 936,
'Rho': 929,
'Scaron': 352,
'Sigma': 931,
'THORN': 222,
'Tau': 932,
'Theta': 920,
'Uacute': 218,
'Ucirc': 219,
'Ugrave': 217,
'Upsilon': 933,
'Uuml': 220,
'Xi': 926,
'Yacute': 221,
'Yuml': 376,
'Zeta': 918,
'aacute': 225,
'acirc': 226,
'acute': 180,
'aelig': 230,
'agrave': 224,
'alefsym': 8501,
'alpha': 945,
'amp': 38,
'and': 8743,
'ang': 8736,
'apos': 39,
'aring': 229,
'asymp': 8776,
'atilde': 227,
'auml': 228,
'bdquo': 8222,
'beta': 946,
'brvbar': 166,
'bull': 8226,
'cap': 8745,
'ccedil': 231,
'cedil': 184,
'cent': 162,
'chi': 967,
'circ': 710,
'clubs': 9827,
'cong': 8773,
'copy': 169,
'crarr': 8629,
'cup': 8746,
'curren': 164,
'dArr': 8659,
'dagger': 8224,
'darr': 8595,
'deg': 176,
'delta': 948,
'diams': 9830,
'divide': 247,
'eacute': 233,
'ecirc': 234,
'egrave': 232,
'empty': 8709,
'emsp': 8195,
'ensp': 8194,
'epsilon': 949,
'equiv': 8801,
'eta': 951,
'eth': 240,
'euml': 235,
'euro': 8364,
'exist': 8707,
'fnof': 402,
'forall': 8704,
'frac12': 189,
'frac14': 188,
'frac34': 190,
'frasl': 8260,
'gamma': 947,
'ge': 8805,
'gt': 62,
'hArr': 8660,
'harr': 8596,
'hearts': 9829,
'hellip': 8230,
'iacute': 237,
'icirc': 238,
'iexcl': 161,
'igrave': 236,
'image': 8465,
'infin': 8734,
'int': 8747,
'iota': 953,
'iquest': 191,
'isin': 8712,
'iuml': 239,
'kappa': 954,
'lArr': 8656,
'lambda': 955,
'lang': 9001,
'laquo': 171,
'larr': 8592,
'lceil': 8968,
'ldquo': 8220,
'le': 8804,
'lfloor': 8970,
'lowast': 8727,
'loz': 9674,
'lrm': 8206,
'lsaquo': 8249,
'lsquo': 8216,
'lt': 60,
'macr': 175,
'mdash': 8212,
'micro': 181,
'middot': 183,
'minus': 8722,
'mu': 956,
'nabla': 8711,
'nbsp': 160,
'ndash': 8211,
'ne': 8800,
'ni': 8715,
'not': 172,
'notin': 8713,
'nsub': 8836,
'ntilde': 241,
'nu': 957,
'oacute': 243,
'ocirc': 244,
'oelig': 339,
'ograve': 242,
'oline': 8254,
'omega': 969,
'omicron': 959,
'oplus': 8853,
'or': 8744,
'ordf': 170,
'ordm': 186,
'oslash': 248,
'otilde': 245,
'otimes': 8855,
'ouml': 246,
'para': 182,
'part': 8706,
'permil': 8240,
'perp': 8869,
'phi': 966,
'pi': 960,
'piv': 982,
'plusmn': 177,
'pound': 163,
'prime': 8242,
'prod': 8719,
'prop': 8733,
'psi': 968,
'quot': 34,
'rArr': 8658,
'radic': 8730,
'rang': 9002,
'raquo': 187,
'rarr': 8594,
'rceil': 8969,
'rdquo': 8221,
'real': 8476,
'reg': 174,
'rfloor': 8971,
'rho': 961,
'rlm': 8207,
'rsaquo': 8250,
'rsquo': 8217,
'sbquo': 8218,
'scaron': 353,
'sdot': 8901,
'sect': 167,
'shy': 173,
'sigma': 963,
'sigmaf': 962,
'sim': 8764,
'spades': 9824,
'sub': 8834,
'sube': 8838,
'sum': 8721,
'sup': 8835,
'sup1': 185,
'sup2': 178,
'sup3': 179,
'supe': 8839,
'szlig': 223,
'tau': 964,
'there4': 8756,
'theta': 952,
'thetasym': 977,
'thinsp': 8201,
'thorn': 254,
'tilde': 732,
'times': 215,
'trade': 8482,
'uArr': 8657,
'uacute': 250,
'uarr': 8593,
'ucirc': 251,
'ugrave': 249,
'uml': 168,
'upsih': 978,
'upsilon': 965,
'uuml': 252,
'weierp': 8472,
'xi': 958,
'yacute': 253,
'yen': 165,
'yuml': 255,
'zeta': 950,
'zwj': 8205,
'zwnj': 8204
"AElig": 198,
"Aacute": 193,
"Acirc": 194,
"Agrave": 192,
"Alpha": 913,
"Aring": 197,
"Atilde": 195,
"Auml": 196,
"Beta": 914,
"Ccedil": 199,
"Chi": 935,
"Dagger": 8225,
"Delta": 916,
"ETH": 208,
"Eacute": 201,
"Ecirc": 202,
"Egrave": 200,
"Epsilon": 917,
"Eta": 919,
"Euml": 203,
"Gamma": 915,
"Iacute": 205,
"Icirc": 206,
"Igrave": 204,
"Iota": 921,
"Iuml": 207,
"Kappa": 922,
"Lambda": 923,
"Mu": 924,
"Ntilde": 209,
"Nu": 925,
"OElig": 338,
"Oacute": 211,
"Ocirc": 212,
"Ograve": 210,
"Omega": 937,
"Omicron": 927,
"Oslash": 216,
"Otilde": 213,
"Ouml": 214,
"Phi": 934,
"Pi": 928,
"Prime": 8243,
"Psi": 936,
"Rho": 929,
"Scaron": 352,
"Sigma": 931,
"THORN": 222,
"Tau": 932,
"Theta": 920,
"Uacute": 218,
"Ucirc": 219,
"Ugrave": 217,
"Upsilon": 933,
"Uuml": 220,
"Xi": 926,
"Yacute": 221,
"Yuml": 376,
"Zeta": 918,
"aacute": 225,
"acirc": 226,
"acute": 180,
"aelig": 230,
"agrave": 224,
"alefsym": 8501,
"alpha": 945,
"amp": 38,
"and": 8743,
"ang": 8736,
"apos": 39,
"aring": 229,
"asymp": 8776,
"atilde": 227,
"auml": 228,
"bdquo": 8222,
"beta": 946,
"brvbar": 166,
"bull": 8226,
"cap": 8745,
"ccedil": 231,
"cedil": 184,
"cent": 162,
"chi": 967,
"circ": 710,
"clubs": 9827,
"cong": 8773,
"copy": 169,
"crarr": 8629,
"cup": 8746,
"curren": 164,
"dArr": 8659,
"dagger": 8224,
"darr": 8595,
"deg": 176,
"delta": 948,
"diams": 9830,
"divide": 247,
"eacute": 233,
"ecirc": 234,
"egrave": 232,
"empty": 8709,
"emsp": 8195,
"ensp": 8194,
"epsilon": 949,
"equiv": 8801,
"eta": 951,
"eth": 240,
"euml": 235,
"euro": 8364,
"exist": 8707,
"fnof": 402,
"forall": 8704,
"frac12": 189,
"frac14": 188,
"frac34": 190,
"frasl": 8260,
"gamma": 947,
"ge": 8805,
"gt": 62,
"hArr": 8660,
"harr": 8596,
"hearts": 9829,
"hellip": 8230,
"iacute": 237,
"icirc": 238,
"iexcl": 161,
"igrave": 236,
"image": 8465,
"infin": 8734,
"int": 8747,
"iota": 953,
"iquest": 191,
"isin": 8712,
"iuml": 239,
"kappa": 954,
"lArr": 8656,
"lambda": 955,
"lang": 9001,
"laquo": 171,
"larr": 8592,
"lceil": 8968,
"ldquo": 8220,
"le": 8804,
"lfloor": 8970,
"lowast": 8727,
"loz": 9674,
"lrm": 8206,
"lsaquo": 8249,
"lsquo": 8216,
"lt": 60,
"macr": 175,
"mdash": 8212,
"micro": 181,
"middot": 183,
"minus": 8722,
"mu": 956,
"nabla": 8711,
"nbsp": 160,
"ndash": 8211,
"ne": 8800,
"ni": 8715,
"not": 172,
"notin": 8713,
"nsub": 8836,
"ntilde": 241,
"nu": 957,
"oacute": 243,
"ocirc": 244,
"oelig": 339,
"ograve": 242,
"oline": 8254,
"omega": 969,
"omicron": 959,
"oplus": 8853,
"or": 8744,
"ordf": 170,
"ordm": 186,
"oslash": 248,
"otilde": 245,
"otimes": 8855,
"ouml": 246,
"para": 182,
"part": 8706,
"permil": 8240,
"perp": 8869,
"phi": 966,
"pi": 960,
"piv": 982,
"plusmn": 177,
"pound": 163,
"prime": 8242,
"prod": 8719,
"prop": 8733,
"psi": 968,
"quot": 34,
"rArr": 8658,
"radic": 8730,
"rang": 9002,
"raquo": 187,
"rarr": 8594,
"rceil": 8969,
"rdquo": 8221,
"real": 8476,
"reg": 174,
"rfloor": 8971,
"rho": 961,
"rlm": 8207,
"rsaquo": 8250,
"rsquo": 8217,
"sbquo": 8218,
"scaron": 353,
"sdot": 8901,
"sect": 167,
"shy": 173,
"sigma": 963,
"sigmaf": 962,
"sim": 8764,
"spades": 9824,
"sub": 8834,
"sube": 8838,
"sum": 8721,
"sup": 8835,
"sup1": 185,
"sup2": 178,
"sup3": 179,
"supe": 8839,
"szlig": 223,
"tau": 964,
"there4": 8756,
"theta": 952,
"thetasym": 977,
"thinsp": 8201,
"thorn": 254,
"tilde": 732,
"times": 215,
"trade": 8482,
"uArr": 8657,
"uacute": 250,
"uarr": 8593,
"ucirc": 251,
"ugrave": 249,
"uml": 168,
"upsih": 978,
"upsilon": 965,
"uuml": 252,
"weierp": 8472,
"xi": 958,
"yacute": 253,
"yen": 165,
"yuml": 255,
"zeta": 950,
"zwj": 8205,
"zwnj": 8204,
}
+45 -22
View File
@@ -1,36 +1,49 @@
# -*- coding: utf-8 -*-
"""
markupsafe._native
~~~~~~~~~~~~~~~~~~
markupsafe._native
~~~~~~~~~~~~~~~~~~
Native Python implementation the C module is not compiled.
Native Python implementation used when the C module is not compiled.
:copyright: (c) 2010 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
:copyright: 2010 Pallets
:license: BSD-3-Clause
"""
from markupsafe import Markup
from markupsafe._compat import text_type
from . import Markup
from ._compat import text_type
def escape(s):
"""Convert the characters &, <, >, ' and " in string s to HTML-safe
sequences. Use this if you need to display text that might contain
such characters in HTML. Marks return value as markup string.
"""Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
the string with HTML-safe sequences. Use this if you need to display
text that might contain such characters in HTML.
If the object has an ``__html__`` method, it is called and the
return value is assumed to already be safe for HTML.
:param s: An object to be converted to a string and escaped.
:return: A :class:`Markup` string with the escaped text.
"""
if hasattr(s, '__html__'):
return s.__html__()
return Markup(text_type(s)
.replace('&', '&amp;')
.replace('>', '&gt;')
.replace('<', '&lt;')
.replace("'", '&#39;')
.replace('"', '&#34;')
if hasattr(s, "__html__"):
return Markup(s.__html__())
return Markup(
text_type(s)
.replace("&", "&amp;")
.replace(">", "&gt;")
.replace("<", "&lt;")
.replace("'", "&#39;")
.replace('"', "&#34;")
)
def escape_silent(s):
"""Like :func:`escape` but converts `None` into an empty
markup string.
"""Like :func:`escape` but treats ``None`` as the empty string.
Useful with optional values, as otherwise you get the string
``'None'`` when the value is ``None``.
>>> escape(None)
Markup('None')
>>> escape_silent(None)
Markup('')
"""
if s is None:
return Markup()
@@ -38,8 +51,18 @@ def escape_silent(s):
def soft_unicode(s):
"""Make a string unicode if it isn't already. That way a markup
string is not converted back to unicode.
"""Convert an object to a string if it isn't already. This preserves
a :class:`Markup` string rather than converting it back to a basic
string, so it will still be marked as safe and won't be escaped
again.
>>> value = escape('<User 1>')
>>> value
Markup('&lt;User 1&gt;')
>>> escape(str(value))
Markup('&amp;lt;User 1&amp;gt;')
>>> escape(soft_unicode(value))
Markup('&lt;User 1&gt;')
"""
if not isinstance(s, text_type):
s = text_type(s)
+199 -15
View File
@@ -2,33 +2,30 @@
* markupsafe._speedups
* ~~~~~~~~~~~~~~~~~~~~
*
* This module implements functions for automatic escaping in C for better
* performance.
* C implementation of escaping for better performance. Used instead of
* the native Python implementation when compiled.
*
* :copyright: (c) 2010 by Armin Ronacher.
* :license: BSD.
* :copyright: 2010 Pallets
* :license: BSD-3-Clause
*/
#include <Python.h>
#if PY_MAJOR_VERSION < 3
#define ESCAPED_CHARS_TABLE_SIZE 63
#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL)));
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
typedef int Py_ssize_t;
#define PY_SSIZE_T_MAX INT_MAX
#define PY_SSIZE_T_MIN INT_MIN
#endif
static PyObject* markup;
static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
#endif
static PyObject* markup;
static int
init_constants(void)
{
PyObject *module;
#if PY_MAJOR_VERSION < 3
/* mapping of characters to replace */
escaped_chars_repl['"'] = UNICHR("&#34;");
escaped_chars_repl['\''] = UNICHR("&#39;");
@@ -41,6 +38,7 @@ init_constants(void)
escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
escaped_chars_delta_len['&'] = 4;
escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
#endif
/* import markup type so that we can mark the return value */
module = PyImport_ImportModule("markupsafe");
@@ -52,6 +50,7 @@ init_constants(void)
return 1;
}
#if PY_MAJOR_VERSION < 3
static PyObject*
escape_unicode(PyUnicodeObject *in)
{
@@ -112,13 +111,192 @@ escape_unicode(PyUnicodeObject *in)
return (PyObject*)out;
}
#else /* PY_MAJOR_VERSION < 3 */
#define GET_DELTA(inp, inp_end, delta) \
while (inp < inp_end) { \
switch (*inp++) { \
case '"': \
case '\'': \
case '&': \
delta += 4; \
break; \
case '<': \
case '>': \
delta += 3; \
break; \
} \
}
#define DO_ESCAPE(inp, inp_end, outp) \
{ \
Py_ssize_t ncopy = 0; \
while (inp < inp_end) { \
switch (*inp) { \
case '"': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = '#'; \
*outp++ = '3'; \
*outp++ = '4'; \
*outp++ = ';'; \
break; \
case '\'': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = '#'; \
*outp++ = '3'; \
*outp++ = '9'; \
*outp++ = ';'; \
break; \
case '&': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = 'a'; \
*outp++ = 'm'; \
*outp++ = 'p'; \
*outp++ = ';'; \
break; \
case '<': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = 'l'; \
*outp++ = 't'; \
*outp++ = ';'; \
break; \
case '>': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = 'g'; \
*outp++ = 't'; \
*outp++ = ';'; \
break; \
default: \
ncopy++; \
} \
inp++; \
} \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
}
static PyObject*
escape_unicode_kind1(PyUnicodeObject *in)
{
Py_UCS1 *inp = PyUnicode_1BYTE_DATA(in);
Py_UCS1 *inp_end = inp + PyUnicode_GET_LENGTH(in);
Py_UCS1 *outp;
PyObject *out;
Py_ssize_t delta = 0;
GET_DELTA(inp, inp_end, delta);
if (!delta) {
Py_INCREF(in);
return (PyObject*)in;
}
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta,
PyUnicode_IS_ASCII(in) ? 127 : 255);
if (!out)
return NULL;
inp = PyUnicode_1BYTE_DATA(in);
outp = PyUnicode_1BYTE_DATA(out);
DO_ESCAPE(inp, inp_end, outp);
return out;
}
static PyObject*
escape_unicode_kind2(PyUnicodeObject *in)
{
Py_UCS2 *inp = PyUnicode_2BYTE_DATA(in);
Py_UCS2 *inp_end = inp + PyUnicode_GET_LENGTH(in);
Py_UCS2 *outp;
PyObject *out;
Py_ssize_t delta = 0;
GET_DELTA(inp, inp_end, delta);
if (!delta) {
Py_INCREF(in);
return (PyObject*)in;
}
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 65535);
if (!out)
return NULL;
inp = PyUnicode_2BYTE_DATA(in);
outp = PyUnicode_2BYTE_DATA(out);
DO_ESCAPE(inp, inp_end, outp);
return out;
}
static PyObject*
escape_unicode_kind4(PyUnicodeObject *in)
{
Py_UCS4 *inp = PyUnicode_4BYTE_DATA(in);
Py_UCS4 *inp_end = inp + PyUnicode_GET_LENGTH(in);
Py_UCS4 *outp;
PyObject *out;
Py_ssize_t delta = 0;
GET_DELTA(inp, inp_end, delta);
if (!delta) {
Py_INCREF(in);
return (PyObject*)in;
}
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 1114111);
if (!out)
return NULL;
inp = PyUnicode_4BYTE_DATA(in);
outp = PyUnicode_4BYTE_DATA(out);
DO_ESCAPE(inp, inp_end, outp);
return out;
}
static PyObject*
escape_unicode(PyUnicodeObject *in)
{
if (PyUnicode_READY(in))
return NULL;
switch (PyUnicode_KIND(in)) {
case PyUnicode_1BYTE_KIND:
return escape_unicode_kind1(in);
case PyUnicode_2BYTE_KIND:
return escape_unicode_kind2(in);
case PyUnicode_4BYTE_KIND:
return escape_unicode_kind4(in);
}
assert(0); /* shouldn't happen */
return NULL;
}
#endif /* PY_MAJOR_VERSION < 3 */
static PyObject*
escape(PyObject *self, PyObject *text)
{
static PyObject *id_html;
PyObject *s = NULL, *rv = NULL, *html;
if (id_html == NULL) {
#if PY_MAJOR_VERSION < 3
id_html = PyString_InternFromString("__html__");
#else
id_html = PyUnicode_InternFromString("__html__");
#endif
if (id_html == NULL) {
return NULL;
}
}
/* we don't have to escape integers, bools or floats */
if (PyLong_CheckExact(text) ||
#if PY_MAJOR_VERSION < 3
@@ -129,10 +307,16 @@ escape(PyObject *self, PyObject *text)
return PyObject_CallFunctionObjArgs(markup, text, NULL);
/* if the object has an __html__ method that performs the escaping */
html = PyObject_GetAttrString(text, "__html__");
html = PyObject_GetAttr(text ,id_html);
if (html) {
rv = PyObject_CallObject(html, NULL);
s = PyObject_CallObject(html, NULL);
Py_DECREF(html);
if (s == NULL) {
return NULL;
}
/* Convert to Markup object */
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
Py_DECREF(s);
return rv;
}
+31
View File
@@ -0,0 +1,31 @@
Build Amazing Things.
***
### Unlicense
This is free and unencumbered software released into the public\
domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute\
this software, either in source code form or as a compiled binary, for any\
purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of\
this software dedicate any and all copyright interest in the software to the\
public domain. We make this dedication for the benefit of the public at\
large and to the detriment of our heirs and successors. We intend this\
dedication to be an overt act of relinquishment in perpetuity of all\
present and future rights to this software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org/>
+21
View File
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
#
# omdict - Ordered Multivalue Dictionary.
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: Build Amazing Things (Unlicense)
from __future__ import absolute_import
from .orderedmultidict import * # noqa
__title__ = 'orderedmultidict'
__version__ = '1.0'
__author__ = 'Ansgar Grunseid'
__contact__ = 'grunseid@gmail.com'
__license__ = 'Unlicense'
__url__ = 'https://github.com/gruns/orderedmultidict'
+157
View File
@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
#
# omdict - Ordered Multivalue Dictionary.
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: Build Amazing Things (Unlicense)
from __future__ import absolute_import
from six.moves import zip_longest
_absent = object() # Marker that means no parameter was provided.
class itemnode(object):
"""
Dictionary key:value items wrapped in a node to be members of itemlist, the
doubly linked list defined below.
"""
def __init__(self, prev=None, next=None, key=_absent, value=_absent):
self.prev = prev
self.next = next
self.key = key
self.value = value
class itemlist(object):
"""
Doubly linked list of itemnodes.
This class is used as the key:value item storage of orderedmultidict.
Methods below were only added as needed for use with orderedmultidict, so
some otherwise common list methods may be missing.
"""
def __init__(self, items=[]):
self.root = itemnode()
self.root.next = self.root.prev = self.root
self.size = 0
for key, value in items:
self.append(key, value)
def append(self, key, value):
tail = self.root.prev if self.root.prev is not self.root else self.root
node = itemnode(tail, self.root, key=key, value=value)
tail.next = node
self.root.prev = node
self.size += 1
return node
def removenode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
self.size -= 1
return self
def clear(self):
for node, key, value in self:
self.removenode(node)
return self
def items(self):
return list(self.iteritems())
def keys(self):
return list(self.iterkeys())
def values(self):
return list(self.itervalues())
def iteritems(self):
for node, key, value in self:
yield key, value
def iterkeys(self):
for node, key, value in self:
yield key
def itervalues(self):
for node, key, value in self:
yield value
def reverse(self):
for node, key, value in self:
node.prev, node.next = node.next, node.prev
self.root.prev, self.root.next = self.root.next, self.root.prev
return self
def __len__(self):
return self.size
def __iter__(self):
current = self.root.next
while current and current is not self.root:
# Record current.next here in case current.next changes after the
# yield and before we return for the next iteration. For example,
# methods like reverse() will change current.next() before yield
# gets executed again.
nextnode = current.next
yield current, current.key, current.value
current = nextnode
def __contains__(self, item):
"""
Params:
item: Can either be a (key,value) tuple or an itemnode reference.
"""
node = key = value = _absent
if hasattr(item, '__len__') and callable(item.__len__):
if len(item) == 2:
key, value = item
elif len(item) == 3:
node, key, value = item
else:
node = item
if node is not _absent or _absent not in [key, value]:
for selfnode, selfkey, selfvalue in self:
if ((node is _absent and key == selfkey and value == selfvalue)
or (node is not _absent and node == selfnode)):
return True
return False
def __getitem__(self, index):
# Only support direct access to the first or last element, as this is
# all orderedmultidict needs for now.
if index == 0 and self.root.next is not self.root:
return self.root.next
elif index == -1 and self.root.prev is not self.root:
return self.root.prev
raise IndexError(index)
def __delitem__(self, index):
self.removenode(self[index])
def __eq__(self, other):
for (n1, key1, value1), (n2, key2, value2) in zip_longest(self, other):
if key1 != key2 or value1 != value2:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __nonzero__(self):
return self.size > 0
def __str__(self):
return '[%s]' % self.items()
+811
View File
@@ -0,0 +1,811 @@
# -*- coding: utf-8 -*-
#
# omdict - Ordered Multivalue Dictionary.
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: Build Amazing Things (Unlicense)
from __future__ import absolute_import
from itertools import chain
from collections import MutableMapping
import six
from six.moves import map, zip_longest
from .itemlist import itemlist
try:
from collections import OrderedDict as odict # Python 2.7 and later.
except ImportError:
from ordereddict import OrderedDict as odict # Python 2.6 and earlier.
import sys
items_attr = 'items' if sys.version_info[0] >= 3 else 'iteritems'
_absent = object() # Marker that means no parameter was provided.
def callable_attr(obj, attr):
return hasattr(obj, attr) and callable(getattr(obj, attr))
#
# TODO(grun): Create a subclass of list that values(), getlist(), allitems(),
# etc return that the user can manipulate directly to control the omdict()
# object.
#
# For example, users should be able to do things like
#
# omd = omdict([(1,1), (1,11)])
# omd.values(1).append('sup')
# omd.allitems() == [(1,1), (1,11), (1,'sup')]
# omd.values(1).remove(11)
# omd.allitems() == [(1,1), (1,'sup')]
# omd.values(1).extend(['two', 'more'])
# omd.allitems() == [(1,1), (1,'sup'), (1,'two'), (1,'more')]
#
# or
#
# omd = omdict([(1,1), (1,11)])
# omd.allitems().extend([(2,2), (2,22)])
# omd.allitems() == [(1,1), (1,11), (2,2), (2,22)])
#
# or
#
# omd = omdict()
# omd.values(1) = [1, 11]
# omd.allitems() == [(1,1), (1,11)]
# omd.values(1) = list(map(lambda i: i * -10, omd.values(1)))
# omd.allitems() == [(1,-10), (1,-110)]
# omd.allitems() = filter(lambda (k,v): v > -100, omd.allitems())
# omd.allitems() == [(1,-10)]
#
# etc.
#
# To accomplish this, subclass list in such a manner that each list element is
# really a two tuple, where the first tuple value is the actual value and the
# second tuple value is a reference to the itemlist node for that value. Users
# only interact with the first tuple values, the actual values, but behind the
# scenes when an element is modified, deleted, inserted, etc, the according
# itemlist nodes are modified, deleted, inserted, etc accordingly. In this
# manner, users can manipulate omdict objects directly through direct list
# manipulation.
#
# Once accomplished, some methods become redundant and should be removed in
# favor of the more intuitive direct value list manipulation. Such redundant
# methods include getlist() (removed in favor of values()?), addlist(), and
# setlist().
#
# With the removal of many of the 'list' methods, think about renaming all
# remaining 'list' methods to 'values' methods, like poplist() -> popvalues(),
# poplistitem() -> popvaluesitem(), etc. This would be an easy switch for most
# methods, but wouldn't fit others so well. For example, iterlists() would
# become itervalues(), a name extremely similar to iterallvalues() but quite
# different in function.
#
class omdict(MutableMapping):
"""
Ordered Multivalue Dictionary.
A multivalue dictionary is a dictionary that can store multiple values per
key. An ordered multivalue dictionary is a multivalue dictionary that
retains the order of insertions and deletions.
Internally, items are stored in a doubly linked list, self._items. A
dictionary, self._map, is also maintained and stores an ordered list of
linked list node references, one for each value associated with that key.
Standard dict methods interact with the first value associated with a given
key. This means that omdict retains method parity with dict, and a dict
object can be replaced with an omdict object and all interaction will
behave identically. All dict methods that retain parity with omdict are:
get(), setdefault(), pop(), popitem(),
clear(), copy(), update(), fromkeys(), len()
__getitem__(), __setitem__(), __delitem__(), __contains__(),
items(), keys(), values(), iteritems(), iterkeys(), itervalues(),
Optional parameters have been added to some dict methods, but because the
added parameters are optional, existing use remains unaffected. An optional
<key> parameter has been added to these methods:
items(), values(), iteritems(), itervalues()
New methods have also been added to omdict. Methods with 'list' in their
name interact with lists of values, and methods with 'all' in their name
interact with all items in the dictionary, including multiple items with
the same key.
The new omdict methods are:
load(), size(), reverse(),
getlist(), add(), addlist(), set(), setlist(), setdefaultlist(),
poplist(), popvalue(), popvalues(), popitem(), poplistitem(),
allitems(), allkeys(), allvalues(), lists(), listitems(),
iterallitems(), iterallkeys(), iterallvalues(), iterlists(),
iterlistitems()
Explanations and examples of the new methods above can be found in the
function comments below and online at
https://github.com/gruns/orderedmultidict
Additional omdict information and documentation can also be found at the
above url.
"""
def __init__(self, *args, **kwargs):
# Doubly linked list of itemnodes. Each itemnode stores a key:value
# item.
self._items = itemlist()
# Ordered dictionary of keys and itemnode references. Each itemnode
# reference points to one of that keys values.
self._map = odict()
self.load(*args, **kwargs)
def load(self, *args, **kwargs):
"""
Clear all existing key:value items and import all key:value items from
<mapping>. If multiple values exist for the same key in <mapping>, they
are all be imported.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.load([(4,4), (4,44), (5,5)])
omd.allitems() == [(4,4), (4,44), (5,5)]
Returns: <self>.
"""
self.clear()
self.updateall(*args, **kwargs)
return self
def copy(self):
return self.__class__(self.allitems())
def clear(self):
self._map.clear()
self._items.clear()
def size(self):
"""
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.size() == 5
Returns: Total number of items, including multiple items with the same
key.
"""
return len(self._items)
@classmethod
def fromkeys(cls, iterable, value=None):
return cls([(key, value) for key in iterable])
def has_key(self, key):
return key in self
def update(self, *args, **kwargs):
self._update_updateall(True, *args, **kwargs)
def updateall(self, *args, **kwargs):
"""
Update this dictionary with the items from <mapping>, replacing
existing key:value items with shared keys before adding new key:value
items.
Example:
omd = omdict([(1,1), (2,2)])
omd.updateall([(2,'two'), (1,'one'), (2,222), (1,111)])
omd.allitems() == [(1, 'one'), (2, 'two'), (2, 222), (1, 111)]
Returns: <self>.
"""
self._update_updateall(False, *args, **kwargs)
return self
def _update_updateall(self, replace_at_most_one, *args, **kwargs):
# Bin the items in <args> and <kwargs> into <replacements> or
# <leftovers>. Items in <replacements> are new values to replace old
# values for a given key, and items in <leftovers> are new items to be
# added.
replacements, leftovers = dict(), []
for mapping in chain(args, [kwargs]):
self._bin_update_items(
self._items_iterator(mapping), replace_at_most_one,
replacements, leftovers)
# First, replace existing values for each key.
for key, values in six.iteritems(replacements):
self.setlist(key, values)
# Then, add the leftover items to the end of the list of all items.
for key, value in leftovers:
self.add(key, value)
def _bin_update_items(self, items, replace_at_most_one,
replacements, leftovers):
"""
<replacements and <leftovers> are modified directly, ala pass by
reference.
"""
for key, value in items:
# If there are existing items with key <key> that have yet to be
# marked for replacement, mark that item's value to be replaced by
# <value> by appending it to <replacements>.
if key in self and key not in replacements:
replacements[key] = [value]
elif (key in self and not replace_at_most_one and
len(replacements[key]) < len(self.values(key))):
replacements[key].append(value)
else:
if replace_at_most_one:
replacements[key] = [value]
else:
leftovers.append((key, value))
def _items_iterator(self, container):
cont = container
iterator = iter(cont)
if callable_attr(cont, 'iterallitems'):
iterator = cont.iterallitems()
elif callable_attr(cont, 'allitems'):
iterator = iter(cont.allitems())
elif callable_attr(cont, 'iteritems'):
iterator = cont.iteritems()
elif callable_attr(cont, 'items'):
iterator = iter(cont.items())
return iterator
def get(self, key, default=None):
if key in self:
return self._map[key][0].value
return default
def getlist(self, key, default=[]):
"""
Returns: The list of values for <key> if <key> is in the dictionary,
else <default>. If <default> is not provided, an empty list is
returned.
"""
if key in self:
return [node.value for node in self._map[key]]
return default
def setdefault(self, key, default=None):
if key in self:
return self[key]
self.add(key, default)
return default
def setdefaultlist(self, key, defaultlist=[None]):
"""
Similar to setdefault() except <defaultlist> is a list of values to set
for <key>. If <key> already exists, its existing list of values is
returned.
If <key> isn't a key and <defaultlist> is an empty list, [], no values
are added for <key> and <key> will not be added as a key.
Returns: List of <key>'s values if <key> exists in the dictionary,
otherwise <default>.
"""
if key in self:
return self.getlist(key)
self.addlist(key, defaultlist)
return defaultlist
def add(self, key, value=None):
"""
Add <value> to the list of values for <key>. If <key> is not in the
dictionary, then <value> is added as the sole value for <key>.
Example:
omd = omdict()
omd.add(1, 1) # omd.allitems() == [(1,1)]
omd.add(1, 11) # omd.allitems() == [(1,1), (1,11)]
omd.add(2, 2) # omd.allitems() == [(1,1), (1,11), (2,2)]
Returns: <self>.
"""
self._map.setdefault(key, [])
node = self._items.append(key, value)
self._map[key].append(node)
return self
def addlist(self, key, valuelist=[]):
"""
Add the values in <valuelist> to the list of values for <key>. If <key>
is not in the dictionary, the values in <valuelist> become the values
for <key>.
Example:
omd = omdict([(1,1)])
omd.addlist(1, [11, 111])
omd.allitems() == [(1, 1), (1, 11), (1, 111)]
omd.addlist(2, [2])
omd.allitems() == [(1, 1), (1, 11), (1, 111), (2, 2)]
Returns: <self>.
"""
for value in valuelist:
self.add(key, value)
return self
def set(self, key, value=None):
"""
Sets <key>'s value to <value>. Identical in function to __setitem__().
Returns: <self>.
"""
self[key] = value
return self
def setlist(self, key, values):
"""
Sets <key>'s list of values to <values>. Existing items with key <key>
are first replaced with new values from <values>. Any remaining old
items that haven't been replaced with new values are deleted, and any
new values from <values> that don't have corresponding items with <key>
to replace are appended to the end of the list of all items.
If values is an empty list, [], <key> is deleted, equivalent in action
to del self[<key>].
Example:
omd = omdict([(1,1), (2,2)])
omd.setlist(1, [11, 111])
omd.allitems() == [(1,11), (2,2), (1,111)]
omd = omdict([(1,1), (1,11), (2,2), (1,111)])
omd.setlist(1, [None])
omd.allitems() == [(1,None), (2,2)]
omd = omdict([(1,1), (1,11), (2,2), (1,111)])
omd.setlist(1, [])
omd.allitems() == [(2,2)]
Returns: <self>.
"""
if not values and key in self:
self.pop(key)
else:
it = zip_longest(
list(self._map.get(key, [])), values, fillvalue=_absent)
for node, value in it:
if node is not _absent and value is not _absent:
node.value = value
elif node is _absent:
self.add(key, value)
elif value is _absent:
self._map[key].remove(node)
self._items.removenode(node)
return self
def removevalues(self, key, values):
"""
Removes all <values> from the values of <key>. If <key> has no
remaining values after removevalues(), the key is popped.
Example:
omd = omdict([(1, 1), (1, 11), (1, 1), (1, 111)])
omd.removevalues(1, [1, 111])
omd.allitems() == [(1, 11)]
Returns: <self>.
"""
self.setlist(key, [v for v in self.getlist(key) if v not in values])
return self
def pop(self, key, default=_absent):
if key in self:
return self.poplist(key)[0]
elif key not in self._map and default is not _absent:
return default
raise KeyError(key)
def poplist(self, key, default=_absent):
"""
If <key> is in the dictionary, pop it and return its list of values. If
<key> is not in the dictionary, return <default>. KeyError is raised if
<default> is not provided and <key> is not in the dictionary.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.poplist(1) == [1, 11, 111]
omd.allitems() == [(2,2), (3,3)]
omd.poplist(2) == [2]
omd.allitems() == [(3,3)]
Raises: KeyError if <key> isn't in the dictionary and <default> isn't
provided.
Returns: List of <key>'s values.
"""
if key in self:
values = self.getlist(key)
del self._map[key]
for node, nodekey, nodevalue in self._items:
if nodekey == key:
self._items.removenode(node)
return values
elif key not in self._map and default is not _absent:
return default
raise KeyError(key)
def popvalue(self, key, value=_absent, default=_absent, last=True):
"""
If <value> is provided, pops the first or last (key,value) item in the
dictionary if <key> is in the dictionary.
If <value> is not provided, pops the first or last value for <key> if
<key> is in the dictionary.
If <key> no longer has any values after a popvalue() call, <key> is
removed from the dictionary. If <key> isn't in the dictionary and
<default> was provided, return default. KeyError is raised if <default>
is not provided and <key> is not in the dictionary. ValueError is
raised if <value> is provided but isn't a value for <key>.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3), (2,22)])
omd.popvalue(1) == 111
omd.allitems() == [(1,11), (1,111), (2,2), (3,3), (2,22)]
omd.popvalue(1, last=False) == 1
omd.allitems() == [(1,11), (2,2), (3,3), (2,22)]
omd.popvalue(2, 2) == 2
omd.allitems() == [(1,11), (3,3), (2,22)]
omd.popvalue(1, 11) == 11
omd.allitems() == [(3,3), (2,22)]
omd.popvalue('not a key', default='sup') == 'sup'
Params:
last: Boolean whether to return <key>'s first value (<last> is False)
or last value (<last> is True).
Raises:
KeyError if <key> isn't in the dictionary and <default> isn't
provided.
ValueError if <value> isn't a value for <key>.
Returns: The first or last of <key>'s values.
"""
def pop_node_with_index(key, index):
node = self._map[key].pop(index)
if not self._map[key]:
del self._map[key]
self._items.removenode(node)
return node
if key in self:
if value is not _absent:
if last:
pos = self.values(key)[::-1].index(value)
else:
pos = self.values(key).index(value)
if pos == -1:
raise ValueError(value)
else:
index = (len(self.values(key)) - 1 - pos) if last else pos
return pop_node_with_index(key, index).value
else:
return pop_node_with_index(key, -1 if last else 0).value
elif key not in self._map and default is not _absent:
return default
raise KeyError(key)
def popitem(self, fromall=False, last=True):
"""
Pop and return a key:value item.
If <fromall> is False, items()[0] is popped if <last> is False or
items()[-1] is popped if <last> is True. All remaining items with the
same key are removed.
If <fromall> is True, allitems()[0] is popped if <last> is False or
allitems()[-1] is popped if <last> is True. Any remaining items with
the same key remain.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.popitem() == (3,3)
omd.popitem(fromall=False, last=False) == (1,1)
omd.popitem(fromall=False, last=False) == (2,2)
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.popitem(fromall=True, last=False) == (1,1)
omd.popitem(fromall=True, last=False) == (1,11)
omd.popitem(fromall=True, last=True) == (3,3)
omd.popitem(fromall=True, last=False) == (1,111)
Params:
fromall: Whether to pop an item from items() (<fromall> is True) or
allitems() (<fromall> is False).
last: Boolean whether to pop the first item or last item of items()
or allitems().
Raises: KeyError if the dictionary is empty.
Returns: The first or last item from item() or allitem().
"""
if not self._items:
raise KeyError('popitem(): %s is empty' % self.__class__.__name__)
if fromall:
node = self._items[-1 if last else 0]
key = node.key
return key, self.popvalue(key, last=last)
else:
key = list(self._map.keys())[-1 if last else 0]
return key, self.pop(key)
def poplistitem(self, last=True):
"""
Pop and return a key:valuelist item comprised of a key and that key's
list of values. If <last> is False, a key:valuelist item comprised of
keys()[0] and its list of values is popped and returned. If <last> is
True, a key:valuelist item comprised of keys()[-1] and its list of
values is popped and returned.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.poplistitem(last=True) == (3,[3])
omd.poplistitem(last=False) == (1,[1,11,111])
Params:
last: Boolean whether to pop the first or last key and its associated
list of values.
Raises: KeyError if the dictionary is empty.
Returns: A two-tuple comprised of the first or last key and its
associated list of values.
"""
if not self._items:
s = 'poplistitem(): %s is empty' % self.__class__.__name__
raise KeyError(s)
key = self.keys()[-1 if last else 0]
return key, self.poplist(key)
def items(self, key=_absent):
"""
Raises: KeyError if <key> is provided and not in the dictionary.
Returns: List created from iteritems(<key>). Only items with key <key>
are returned if <key> is provided and is a dictionary key.
"""
return list(self.iteritems(key))
def keys(self):
return list(self.iterkeys())
def values(self, key=_absent):
"""
Raises: KeyError if <key> is provided and not in the dictionary.
Returns: List created from itervalues(<key>).If <key> is provided and
is a dictionary key, only values of items with key <key> are
returned.
"""
if key is not _absent and key in self._map:
return self.getlist(key)
return list(self.itervalues())
def lists(self):
"""
Returns: List created from iterlists().
"""
return list(self.iterlists())
def listitems(self):
"""
Returns: List created from iterlistitems().
"""
return list(self.iterlistitems())
def iteritems(self, key=_absent):
"""
Parity with dict.iteritems() except the optional <key> parameter has
been added. If <key> is provided, only items with the provided key are
iterated over. KeyError is raised if <key> is provided and not in the
dictionary.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.iteritems(1) -> (1,1) -> (1,11) -> (1,111)
omd.iteritems() -> (1,1) -> (2,2) -> (3,3)
Raises: KeyError if <key> is provided and not in the dictionary.
Returns: An iterator over the items() of the dictionary, or only items
with the key <key> if <key> is provided.
"""
if key is not _absent:
if key in self:
items = [(node.key, node.value) for node in self._map[key]]
return iter(items)
raise KeyError(key)
items = six.iteritems(self._map)
return iter((key, nodes[0].value) for (key, nodes) in items)
def iterkeys(self):
return six.iterkeys(self._map)
def itervalues(self, key=_absent):
"""
Parity with dict.itervalues() except the optional <key> parameter has
been added. If <key> is provided, only values from items with the
provided key are iterated over. KeyError is raised if <key> is provided
and not in the dictionary.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.itervalues(1) -> 1 -> 11 -> 111
omd.itervalues() -> 1 -> 11 -> 111 -> 2 -> 3
Raises: KeyError if <key> is provided and isn't in the dictionary.
Returns: An iterator over the values() of the dictionary, or only the
values of key <key> if <key> is provided.
"""
if key is not _absent:
if key in self:
return iter([node.value for node in self._map[key]])
raise KeyError(key)
return iter([nodes[0].value for nodes in six.itervalues(self._map)])
def allitems(self, key=_absent):
'''
Raises: KeyError if <key> is provided and not in the dictionary.
Returns: List created from iterallitems(<key>).
'''
return list(self.iterallitems(key))
def allkeys(self):
'''
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.allkeys() == [1,1,1,2,3]
Returns: List created from iterallkeys().
'''
return list(self.iterallkeys())
def allvalues(self, key=_absent):
'''
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.allvalues() == [1,11,111,2,3]
omd.allvalues(1) == [1,11,111]
Raises: KeyError if <key> is provided and not in the dictionary.
Returns: List created from iterallvalues(<key>).
'''
return list(self.iterallvalues(key))
def iterallitems(self, key=_absent):
'''
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.iterallitems() == (1,1) -> (1,11) -> (1,111) -> (2,2) -> (3,3)
omd.iterallitems(1) == (1,1) -> (1,11) -> (1,111)
Raises: KeyError if <key> is provided and not in the dictionary.
Returns: An iterator over every item in the diciontary. If <key> is
provided, only items with the key <key> are iterated over.
'''
if key is not _absent:
# Raises KeyError if <key> is not in self._map.
return self.iteritems(key)
return self._items.iteritems()
def iterallkeys(self):
'''
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.iterallkeys() == 1 -> 1 -> 1 -> 2 -> 3
Returns: An iterator over the keys of every item in the dictionary.
'''
return self._items.iterkeys()
def iterallvalues(self, key=_absent):
'''
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.iterallvalues() == 1 -> 11 -> 111 -> 2 -> 3
Returns: An iterator over the values of every item in the dictionary.
'''
if key is not _absent:
if key in self:
return iter(self.getlist(key))
raise KeyError(key)
return self._items.itervalues()
def iterlists(self):
'''
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.iterlists() -> [1,11,111] -> [2] -> [3]
Returns: An iterator over the list comprised of the lists of values for
each key.
'''
return map(lambda key: self.getlist(key), self)
def iterlistitems(self):
"""
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.iterlistitems() -> (1,[1,11,111]) -> (2,[2]) -> (3,[3])
Returns: An iterator over the list of key:valuelist items.
"""
return map(lambda key: (key, self.getlist(key)), self)
def reverse(self):
"""
Reverse the order of all items in the dictionary.
Example:
omd = omdict([(1,1), (1,11), (1,111), (2,2), (3,3)])
omd.reverse()
omd.allitems() == [(3,3), (2,2), (1,111), (1,11), (1,1)]
Returns: <self>.
"""
for key in six.iterkeys(self._map):
self._map[key].reverse()
self._items.reverse()
return self
def __eq__(self, other):
if callable_attr(other, 'iterallitems'):
myiter, otheriter = self.iterallitems(), other.iterallitems()
for i1, i2 in zip_longest(myiter, otheriter, fillvalue=_absent):
if i1 != i2 or i1 is _absent or i2 is _absent:
return False
elif not hasattr(other, '__len__') or not hasattr(other, items_attr):
return False
# Ignore order so we can compare ordered omdicts with unordered dicts.
else:
if len(self) != len(other):
return False
for key, value in six.iteritems(other):
if self.get(key, _absent) != value:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __len__(self):
return len(self._map)
def __iter__(self):
for key in self.iterkeys():
yield key
def __contains__(self, key):
return key in self._map
def __getitem__(self, key):
if key in self:
return self.get(key)
raise KeyError(key)
def __setitem__(self, key, value):
self.setlist(key, [value])
def __delitem__(self, key):
return self.pop(key)
def __nonzero__(self):
return bool(self._map)
def __str__(self):
return '{%s}' % ', '.join(
map(lambda p: '%r: %r' % (p[0], p[1]), self.iterallitems()))
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.allitems())
+10 -4
View File
@@ -4,18 +4,24 @@
from __future__ import absolute_import, division, print_function
__all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__",
"__email__", "__license__", "__copyright__",
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
]
__title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__version__ = "18.0"
__version__ = "19.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
__copyright__ = "Copyright 2014-2018 %s" % __author__
__copyright__ = "Copyright 2014-2019 %s" % __author__
+16 -4
View File
@@ -4,11 +4,23 @@
from __future__ import absolute_import, division, print_function
from .__about__ import (
__author__, __copyright__, __email__, __license__, __summary__, __title__,
__uri__, __version__
__author__,
__copyright__,
__email__,
__license__,
__summary__,
__title__,
__uri__,
__version__,
)
__all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__",
"__email__", "__license__", "__copyright__",
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
]
+4 -3
View File
@@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3
# flake8: noqa
if PY3:
string_types = str,
string_types = (str,)
else:
string_types = basestring,
string_types = (basestring,)
def with_metaclass(meta, *bases):
@@ -27,4 +27,5 @@ def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
return type.__new__(metaclass, "temporary_class", (), {})
-2
View File
@@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function
class Infinity(object):
def __repr__(self):
return "Infinity"
@@ -38,7 +37,6 @@ Infinity = Infinity()
class NegativeInfinity(object):
def __repr__(self):
return "-Infinity"
+42 -47
View File
@@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier
__all__ = [
"InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName",
"Marker", "default_environment",
"InvalidMarker",
"UndefinedComparison",
"UndefinedEnvironmentName",
"Marker",
"default_environment",
]
@@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError):
class Node(object):
def __init__(self, value):
self.value = value
@@ -57,62 +59,52 @@ class Node(object):
class Variable(Node):
def serialize(self):
return str(self)
class Value(Node):
def serialize(self):
return '"{0}"'.format(self)
class Op(Node):
def serialize(self):
return str(self)
VARIABLE = (
L("implementation_version") |
L("platform_python_implementation") |
L("implementation_name") |
L("python_full_version") |
L("platform_release") |
L("platform_version") |
L("platform_machine") |
L("platform_system") |
L("python_version") |
L("sys_platform") |
L("os_name") |
L("os.name") | # PEP-345
L("sys.platform") | # PEP-345
L("platform.version") | # PEP-345
L("platform.machine") | # PEP-345
L("platform.python_implementation") | # PEP-345
L("python_implementation") | # undocumented setuptools legacy
L("extra")
L("implementation_version")
| L("platform_python_implementation")
| L("implementation_name")
| L("python_full_version")
| L("platform_release")
| L("platform_version")
| L("platform_machine")
| L("platform_system")
| L("python_version")
| L("sys_platform")
| L("os_name")
| L("os.name")
| L("sys.platform") # PEP-345
| L("platform.version") # PEP-345
| L("platform.machine") # PEP-345
| L("platform.python_implementation") # PEP-345
| L("python_implementation") # PEP-345
| L("extra") # undocumented setuptools legacy
)
ALIASES = {
'os.name': 'os_name',
'sys.platform': 'sys_platform',
'platform.version': 'platform_version',
'platform.machine': 'platform_machine',
'platform.python_implementation': 'platform_python_implementation',
'python_implementation': 'platform_python_implementation'
"os.name": "os_name",
"sys.platform": "sys_platform",
"platform.version": "platform_version",
"platform.machine": "platform_machine",
"platform.python_implementation": "platform_python_implementation",
"python_implementation": "platform_python_implementation",
}
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
L("===") |
L("==") |
L(">=") |
L("<=") |
L("!=") |
L("~=") |
L(">") |
L("<")
L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
)
MARKER_OP = VERSION_CMP | L("not in") | L("in")
@@ -152,8 +144,11 @@ def _format_marker(marker, first=True):
# where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the
# outside.
if (isinstance(marker, list) and len(marker) == 1 and
isinstance(marker[0], (list, tuple))):
if (
isinstance(marker, list)
and len(marker) == 1
and isinstance(marker[0], (list, tuple))
):
return _format_marker(marker[0])
if isinstance(marker, list):
@@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment):
def format_full_version(info):
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != 'final':
if kind != "final":
version += kind[0] + str(info.serial)
return version
def default_environment():
if hasattr(sys, 'implementation'):
if hasattr(sys, "implementation"):
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
else:
iver = '0'
implementation_name = ''
iver = "0"
implementation_name = ""
return {
"implementation_name": implementation_name,
@@ -270,13 +265,13 @@ def default_environment():
class Marker(object):
def __init__(self, marker):
try:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc:e.loc + 8])
marker, marker[e.loc : e.loc + 8]
)
raise InvalidMarker(err_str)
def __str__(self):
+21 -13
View File
@@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name")
EXTRA = IDENTIFIER
URI = Regex(r'[^ ]+')("url")
URL = (AT + URI)
URI = Regex(r"[^ ]+")("url")
URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
@@ -48,17 +48,18 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE),
joinString=",", adjacent=False)("_raw_spec")
VERSION_MANY = Combine(
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
)("_raw_spec")
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '')
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction(
lambda s, l, t: Marker(s[t._original_start:t._original_end])
lambda s, l, t: Marker(s[t._original_start : t._original_end])
)
MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPARATOR + MARKER_EXPR
@@ -66,8 +67,7 @@ MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER)
NAMED_REQUIREMENT = \
NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
# pyparsing isn't thread safe during initialization, so we do it eagerly, see
@@ -92,15 +92,21 @@ class Requirement(object):
try:
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement("Parse error at \"{0!r}\": {1}".format(
requirement_string[e.loc:e.loc + 8], e.msg
))
raise InvalidRequirement(
'Parse error at "{0!r}": {1}'.format(
requirement_string[e.loc : e.loc + 8], e.msg
)
)
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc):
if parsed_url.scheme == "file":
if urlparse.urlunparse(parsed_url) != req.url:
raise InvalidRequirement("Invalid URL given")
elif not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc
):
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
self.url = req.url
else:
@@ -120,6 +126,8 @@ class Requirement(object):
if self.url:
parts.append("@ {0}".format(self.url))
if self.marker:
parts.append(" ")
if self.marker:
parts.append("; {0}".format(self.marker))
+21 -46
View File
@@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError):
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod
def __str__(self):
"""
@@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier):
if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
self._spec = (
match.group("operator").strip(),
match.group("version").strip(),
)
self._spec = (match.group("operator").strip(), match.group("version").strip())
# Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases
@@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier):
else ""
)
return "<{0}({1!r}{2})>".format(
self.__class__.__name__,
str(self),
pre,
)
return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
def __str__(self):
return "{0}{1}".format(*self._spec)
@@ -194,8 +186,9 @@ class _IndividualSpecifier(BaseSpecifier):
# If our version is a prerelease, and we were not set to allow
# prereleases, then we'll store it for later incase nothing
# else matches this specifier.
if (parsed_version.is_prerelease and not
(prereleases or self.prereleases)):
if parsed_version.is_prerelease and not (
prereleases or self.prereleases
):
found_prereleases.append(version)
# Either this is not a prerelease, or we should have been
# accepting prereleases from the beginning.
@@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier):
class LegacySpecifier(_IndividualSpecifier):
_regex_str = (
r"""
_regex_str = r"""
(?P<operator>(==|!=|<=|>=|<|>))
\s*
(?P<version>
@@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier):
# them, and a comma since it's a version separator.
)
"""
)
_regex = re.compile(
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"==": "equal",
@@ -269,13 +259,13 @@ def _require_version_compare(fn):
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
return wrapped
class Specifier(_IndividualSpecifier):
_regex_str = (
r"""
_regex_str = r"""
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
(?P<version>
(?:
@@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier):
)
)
"""
)
_regex = re.compile(
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"~=": "compatible",
@@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier):
prefix = ".".join(
list(
itertools.takewhile(
lambda x: (not x.startswith("post") and not
x.startswith("dev")),
lambda x: (not x.startswith("post") and not x.startswith("dev")),
_version_split(spec),
)
)[:-1]
@@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier):
# Add the prefix notation to the end of our string
prefix += ".*"
return (self._get_operator(">=")(prospective, spec) and
self._get_operator("==")(prospective, prefix))
return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
prospective, prefix
)
@_require_version_compare
def _compare_equal(self, prospective, spec):
@@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier):
# Shorten the prospective version to be the same length as the spec
# so that we can determine if the specifier is a prefix of the
# prospective version or not.
prospective = prospective[:len(spec)]
prospective = prospective[: len(spec)]
# Pad out our two sides with zeros so that they both equal the same
# length.
@@ -567,27 +555,17 @@ def _pad_version(left, right):
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions
left_split.append(left[len(left_split[0]):])
right_split.append(right[len(right_split[0]):])
left_split.append(left[len(left_split[0]) :])
right_split.append(right[len(right_split[0]) :])
# Insert our padding
left_split.insert(
1,
["0"] * max(0, len(right_split[0]) - len(left_split[0])),
)
right_split.insert(
1,
["0"] * max(0, len(left_split[0]) - len(right_split[0])),
)
left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
return (
list(itertools.chain(*left_split)),
list(itertools.chain(*right_split)),
)
return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
class SpecifierSet(BaseSpecifier):
def __init__(self, specifiers="", prereleases=None):
# Split on , to break each indidivual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
@@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier):
# given version is contained within all of them.
# Note: This use of all() here means that an empty set of specifiers
# will always return True, this is an explicit design decision.
return all(
s.contains(item, prereleases=prereleases)
for s in self._specs
)
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(self, iterable, prereleases=None):
# Determine if we're forcing a prerelease or not, if we're not forcing
+1 -7
View File
@@ -36,13 +36,7 @@ def canonicalize_version(version):
# Release segment
# NB: This strips trailing '.0's to normalize
parts.append(
re.sub(
r'(\.0)+$',
'',
".".join(str(x) for x in version.release)
)
)
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release)))
# Pre-release
if version.pre is not None:
+14 -35
View File
@@ -10,14 +10,11 @@ import re
from ._structures import Infinity
__all__ = [
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
]
__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
_Version = collections.namedtuple(
"_Version",
["epoch", "release", "dev", "pre", "post", "local"],
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
)
@@ -40,7 +37,6 @@ class InvalidVersion(ValueError):
class _BaseVersion(object):
def __hash__(self):
return hash(self._key)
@@ -70,7 +66,6 @@ class _BaseVersion(object):
class LegacyVersion(_BaseVersion):
def __init__(self, version):
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
@@ -126,12 +121,14 @@ class LegacyVersion(_BaseVersion):
return False
_legacy_version_component_re = re.compile(
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
)
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
_legacy_version_replacement_map = {
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
"pre": "c",
"preview": "c",
"-": "final-",
"rc": "c",
"dev": "@",
}
@@ -215,10 +212,7 @@ VERSION_PATTERN = r"""
class Version(_BaseVersion):
_regex = re.compile(
r"^\s*" + VERSION_PATTERN + r"\s*$",
re.VERBOSE | re.IGNORECASE,
)
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
def __init__(self, version):
# Validate the version and parse it into pieces
@@ -230,18 +224,11 @@ class Version(_BaseVersion):
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version(
match.group("pre_l"),
match.group("pre_n"),
),
pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
post=_parse_letter_version(
match.group("post_l"),
match.group("post_n1") or match.group("post_n2"),
),
dev=_parse_letter_version(
match.group("dev_l"),
match.group("dev_n"),
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
),
dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
)
@@ -395,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
release = tuple(
reversed(list(
itertools.dropwhile(
lambda x: x == 0,
reversed(release),
)
))
reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
@@ -433,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
local = tuple(
(i, "") if isinstance(i, int) else (-Infinity, i)
for i in local
)
local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
return epoch, release, pre, post, dev, local
+74 -57
View File
@@ -3,7 +3,7 @@ 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()``, ``findall()``,
and ``with_pattern()`` when ``import *`` is used:
and ``with_pattern()`` when ``import \*`` is used:
>>> from parse import *
@@ -78,9 +78,11 @@ Some simple parse() format string examples:
{'item': 'hand grenade'}
>>> print(r['item'])
hand grenade
>>> 'item' in r
True
Dotted names and indexes are possible though the application must make
additional sense of the result:
Note that `in` only works if you have named fields. Dotted names and indexes
are possible though the application must make additional sense of the result:
>>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
>>> print(r)
@@ -132,38 +134,39 @@ The differences between `parse()` and `format()` are:
===== =========================================== ========
Type Characters Matched Output
===== =========================================== ========
w Letters and underscore str
W Non-letter and underscore str
s Whitespace str
S Non-whitespace str
d Digits (effectively integer numbers) int
D Non-digit str
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
l Letters (ASCII) str
w Letters, numbers and underscore str
W Not letters, numbers and underscore str
s Whitespace str
S Non-whitespace str
d Digits (effectively integer numbers) int
D Non-digit str
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
b Binary numbers int
o Octal numbers int
x Hexadecimal numbers (lower and upper case) int
ti ISO 8601 format date/time datetime
g General number format (either d, f or e) float
b Binary numbers int
o Octal numbers int
x Hexadecimal numbers (lower and upper case) int
ti ISO 8601 format date/time datetime
e.g. 1972-01-20T10:21:36Z ("T" and "Z"
optional)
te RFC2822 e-mail format date/time datetime
te RFC2822 e-mail format date/time datetime
e.g. Mon, 20 Jan 1972 10:21:36 +1000
tg Global (day/month) format date/time datetime
tg Global (day/month) format date/time datetime
e.g. 20/1/1972 10:21:36 AM +1:00
ta US (month/day) format date/time datetime
ta US (month/day) format date/time datetime
e.g. 1/20/1972 10:21:36 PM +10:30
tc ctime() format date/time datetime
tc ctime() format date/time datetime
e.g. Sun Sep 16 01:03:52 1973
th HTTP log format date/time datetime
th HTTP log format date/time datetime
e.g. 21/Nov/2011:00:07:11 +0000
ts Linux system log format date/time datetime
ts Linux system log format date/time datetime
e.g. Nov 9 03:37:44
tt Time time
tt Time time
e.g. 10:21:36 PM -5:30
===== =========================================== ========
@@ -342,6 +345,13 @@ the pattern, the actual match represents the shortest successful match for
**Version history (in brief)**:
- 1.11.1 Revert having unicode char in docstring, it breaks Bamboo builds(?!)
- 1.11.0 Implement `__contains__` for Result instances.
- 1.10.0 Introduce a "letters" matcher, since "w" matches numbers
also.
- 1.9.1 Fix deprecation warnings around backslashes in regex strings
(thanks Mickael Schoentgen). Also fix some documentation formatting
issues.
- 1.9.0 We now honor precision and width specifiers when parsing numbers
and strings, allowing parsing of concatenated elements of fixed width
(thanks Julia Signell)
@@ -400,12 +410,12 @@ the pattern, the actual match represents the shortest successful match for
and removed the restriction on mixing fixed-position and named fields
- 1.0.0 initial release
This code is copyright 2012-2017 Richard Jones <richard@python.org>
This code is copyright 2012-2019 Richard Jones <richard@python.org>
See the end of the source file for the license of use.
'''
from __future__ import absolute_import
__version__ = '1.9.0'
__version__ = '1.11.1'
# yes, I now have two problems
import re
@@ -530,9 +540,9 @@ MONTHS_MAP = dict(
Nov=11, November=11,
Dec=12, December=12
)
DAYS_PAT = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
MONTHS_PAT = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
ALL_MONTHS_PAT = '(%s)' % '|'.join(MONTHS_MAP)
DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
ALL_MONTHS_PAT = r'(%s)' % '|'.join(MONTHS_MAP)
TIME_PAT = r'(\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)'
AM_PAT = r'(\s+[AP]M)'
TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)'
@@ -550,11 +560,11 @@ def date_convert(string, match, ymd=None, mdy=None, dmy=None,
m=groups[mm]
d=groups[dd]
elif ymd is not None:
y, m, d = re.split('[-/\s]', groups[ymd])
y, m, d = re.split(r'[-/\s]', groups[ymd])
elif mdy is not None:
m, d, y = re.split('[-/\s]', groups[mdy])
m, d, y = re.split(r'[-/\s]', groups[mdy])
elif dmy is not None:
d, m, y = re.split('[-/\s]', groups[dmy])
d, m, y = re.split(r'[-/\s]', groups[dmy])
elif d_m_y is not None:
d, m, y = d_m_y
d = groups[d]
@@ -636,10 +646,10 @@ class RepeatedNameError(ValueError):
# note: {} are handled separately
# note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit
REGEX_SAFETY = re.compile('([?\\\\.[\]()*+\^$!\|])')
REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])')
# allowed field types
ALLOWED_TYPES = set(list('nbox%fFegwWdDsS') +
ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') +
['t' + c for c in 'ieahgcts'])
@@ -745,7 +755,7 @@ class Parser(object):
@property
def _match_re(self):
if self.__match_re is None:
expression = '^%s$' % self._expression
expression = r'^%s$' % self._expression
try:
self.__match_re = re.compile(expression, self._re_flags)
except AssertionError:
@@ -923,16 +933,16 @@ class Parser(object):
name, self._name_types[name]))
group = self._name_to_group_map[name]
# match previously-seen value
return '(?P=%s)' % group
return r'(?P=%s)' % group
else:
group = self._to_group_name(name)
self._name_types[name] = format
self._named_fields.append(group)
# this will become a group, which must not contain dots
wrap = '(?P<%s>%%s)' % group
wrap = r'(?P<%s>%%s)' % group
else:
self._fixed_fields.append(self._group_index)
wrap = '(%s)'
wrap = r'(%s)'
if ':' in field:
format = field[1:]
group = self._group_index
@@ -940,7 +950,7 @@ class Parser(object):
# simplest case: no type specifier ({} or {name})
if not format:
self._group_index += 1
return wrap % '.+?'
return wrap % r'.+?'
# decode the format specification
format = extract_format(format, self._extra_types)
@@ -960,19 +970,19 @@ class Parser(object):
return type_converter(string)
self._type_conversions[group] = f
elif type == 'n':
s = '\d{1,3}([,.]\d{3})*'
s = r'\d{1,3}([,.]\d{3})*'
self._group_index += 1
self._type_conversions[group] = int_convert(10)
elif type == 'b':
s = '(0[bB])?[01]+'
s = r'(0[bB])?[01]+'
self._type_conversions[group] = int_convert(2)
self._group_index += 1
elif type == 'o':
s = '(0[oO])?[0-7]+'
s = r'(0[oO])?[0-7]+'
self._type_conversions[group] = int_convert(8)
self._group_index += 1
elif type == 'x':
s = '(0[xX])?[0-9a-fA-F]+'
s = r'(0[xX])?[0-9a-fA-F]+'
self._type_conversions[group] = int_convert(16)
self._group_index += 1
elif type == '%':
@@ -994,10 +1004,10 @@ class Parser(object):
self._type_conversions[group] = lambda s, m: float(s)
elif type == 'd':
if format.get('width'):
width = '{1,%s}' % int(format['width'])
width = r'{1,%s}' % int(format['width'])
else:
width = '+'
s = '\\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width)
s = r'\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width)
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)?' % \
@@ -1055,18 +1065,19 @@ class Parser(object):
self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3,
hms=n + 5)
self._group_index += 5
elif type == 'l':
s = r'[A-Za-z]+'
elif type:
s = r'\%s+' % type
elif format.get('precision'):
if format.get('width'):
s = '.{%s,%s}?' % (format['width'], format['precision'])
s = r'.{%s,%s}?' % (format['width'], format['precision'])
else:
s = '.{1,%s}?' % format['precision']
s = r'.{1,%s}?' % format['precision']
elif format.get('width'):
s = '.{%s,}?' % format['width']
s = r'.{%s,}?' % format['width']
else:
s = '.+?'
s = r'.+?'
align = format['align']
fill = format['fill']
@@ -1079,7 +1090,7 @@ class Parser(object):
# configurable fill defaulting to "0"
if not fill:
fill = '0'
s = '%s*' % fill + s
s = r'%s*' % fill + s
# allow numbers to be prefixed with a sign
s = r'[-+ ]?' + s
@@ -1101,7 +1112,7 @@ class Parser(object):
if not align:
align = '>'
if fill in '.\+?*[](){}^$':
if fill in r'.\+?*[](){}^$':
fill = '\\' + fill
# align "=" has been handled
@@ -1118,8 +1129,11 @@ class Parser(object):
class Result(object):
'''The result of a parse() or search().
Fixed results may be looked up using result[index]. Named results may be
looked up using result['name'].
Fixed results may be looked up using `result[index]`.
Named results may be looked up using `result['name']`.
Named results may be tested for existence using `'name' in result`.
'''
def __init__(self, fixed, named, spans):
self.fixed = fixed
@@ -1135,6 +1149,9 @@ class Result(object):
return '<%s %r %r>' % (self.__class__.__name__, self.fixed,
self.named)
def __contains__(self, name):
return name in self.named
class Match(object):
'''The result of a parse() or search() if no results are generated.
@@ -1295,7 +1312,7 @@ def compile(format, extra_types=None, case_sensitive=False):
return Parser(format, extra_types=extra_types)
# Copyright (c) 2012-2013 Richard Jones <richard@python.org>
# Copyright (c) 2012-2019 Richard Jones <richard@python.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
+2 -2
View File
@@ -20,13 +20,13 @@ class Project(passa.models.projects.Project):
pipfile = root.joinpath("Pipfile")
if not pipfile.is_file():
raise argparse.ArgumentError(
"{0!r} is not a Pipfile project".format(root),
"project", "{0!r} is not a Pipfile project".format(root),
)
try:
super(Project, self).__init__(root.as_posix(), *args, **kwargs)
except tomlkit.exceptions.ParseError as e:
raise argparse.ArgumentError(
"failed to parse Pipfile: {0!r}".format(str(e)),
"project", "failed to parse Pipfile: {0!r}".format(str(e)),
)
def __name__(self):
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Thomas Kluyver
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.
+4
View File
@@ -0,0 +1,4 @@
"""Wrappers to build Python packages using PEP 517 hooks
"""
__version__ = '0.5.0'
+207
View File
@@ -0,0 +1,207 @@
"""This is invoked in a subprocess to call the build backend hooks.
It expects:
- Command line args: hook_name, control_dir
- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec
- control_dir/input.json:
- {"kwargs": {...}}
Results:
- control_dir/output.json
- {"return_val": ...}
"""
from glob import glob
from importlib import import_module
import os
from os.path import join as pjoin
import re
import shutil
import sys
# This is run as a script, not a module, so it can't do a relative import
import compat
class BackendUnavailable(Exception):
"""Raised if we cannot import the backend"""
def _build_backend():
"""Find and load the build backend"""
ep = os.environ['PEP517_BUILD_BACKEND']
mod_path, _, obj_path = ep.partition(':')
try:
obj = import_module(mod_path)
except ImportError:
raise BackendUnavailable
if obj_path:
for path_part in obj_path.split('.'):
obj = getattr(obj, path_part)
return obj
def get_requires_for_build_wheel(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_wheel
except AttributeError:
return []
else:
return hook(config_settings)
def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
"""Invoke optional prepare_metadata_for_build_wheel
Implements a fallback by building a wheel if the hook isn't defined.
"""
backend = _build_backend()
try:
hook = backend.prepare_metadata_for_build_wheel
except AttributeError:
return _get_wheel_metadata_from_wheel(backend, metadata_directory,
config_settings)
else:
return hook(metadata_directory, config_settings)
WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL'
def _dist_info_files(whl_zip):
"""Identify the .dist-info folder inside a wheel ZipFile."""
res = []
for path in whl_zip.namelist():
m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path)
if m:
res.append(path)
if res:
return res
raise Exception("No .dist-info folder found in wheel")
def _get_wheel_metadata_from_wheel(
backend, metadata_directory, config_settings):
"""Build a wheel and extract the metadata from it.
Fallback for when the build backend does not
define the 'get_wheel_metadata' hook.
"""
from zipfile import ZipFile
whl_basename = backend.build_wheel(metadata_directory, config_settings)
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'):
pass # Touch marker file
whl_file = os.path.join(metadata_directory, whl_basename)
with ZipFile(whl_file) as zipf:
dist_info = _dist_info_files(zipf)
zipf.extractall(path=metadata_directory, members=dist_info)
return dist_info[0].split('/')[0]
def _find_already_built_wheel(metadata_directory):
"""Check for a wheel already built during the get_wheel_metadata hook.
"""
if not metadata_directory:
return None
metadata_parent = os.path.dirname(metadata_directory)
if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
return None
whl_files = glob(os.path.join(metadata_parent, '*.whl'))
if not whl_files:
print('Found wheel built marker, but no .whl files')
return None
if len(whl_files) > 1:
print('Found multiple .whl files; unspecified behaviour. '
'Will call build_wheel.')
return None
# Exactly one .whl file
return whl_files[0]
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
"""Invoke the mandatory build_wheel hook.
If a wheel was already built in the
prepare_metadata_for_build_wheel fallback, this
will copy it rather than rebuilding the wheel.
"""
prebuilt_whl = _find_already_built_wheel(metadata_directory)
if prebuilt_whl:
shutil.copy2(prebuilt_whl, wheel_directory)
return os.path.basename(prebuilt_whl)
return _build_backend().build_wheel(wheel_directory, config_settings,
metadata_directory)
def get_requires_for_build_sdist(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_sdist
except AttributeError:
return []
else:
return hook(config_settings)
class _DummyException(Exception):
"""Nothing should ever raise this exception"""
class GotUnsupportedOperation(Exception):
"""For internal use when backend raises UnsupportedOperation"""
def build_sdist(sdist_directory, config_settings):
"""Invoke the mandatory build_sdist hook."""
backend = _build_backend()
try:
return backend.build_sdist(sdist_directory, config_settings)
except getattr(backend, 'UnsupportedOperation', _DummyException):
raise GotUnsupportedOperation
HOOK_NAMES = {
'get_requires_for_build_wheel',
'prepare_metadata_for_build_wheel',
'build_wheel',
'get_requires_for_build_sdist',
'build_sdist',
}
def main():
if len(sys.argv) < 3:
sys.exit("Needs args: hook_name, control_dir")
hook_name = sys.argv[1]
control_dir = sys.argv[2]
if hook_name not in HOOK_NAMES:
sys.exit("Unknown hook: %s" % hook_name)
hook = globals()[hook_name]
hook_input = compat.read_json(pjoin(control_dir, 'input.json'))
json_out = {'unsupported': False, 'return_val': None}
try:
json_out['return_val'] = hook(**hook_input['kwargs'])
except BackendUnavailable:
json_out['no_backend'] = True
except GotUnsupportedOperation:
json_out['unsupported'] = True
compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2)
if __name__ == '__main__':
main()
+108
View File
@@ -0,0 +1,108 @@
"""Build a project using PEP 517 hooks.
"""
import argparse
import logging
import os
import contextlib
import pytoml
import shutil
import errno
import tempfile
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
@contextlib.contextmanager
def tempdir():
td = tempfile.mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
def _do_build(hooks, env, dist, dest):
get_requires_name = 'get_requires_for_build_{dist}'.format(**locals())
get_requires = getattr(hooks, get_requires_name)
reqs = get_requires({})
log.info('Got build requires: %s', reqs)
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
with tempdir() as td:
log.info('Trying to build %s in %s', dist, td)
build_name = 'build_{dist}'.format(**locals())
build = getattr(hooks, build_name)
filename = build(td, {})
source = os.path.join(td, filename)
shutil.move(source, os.path.join(dest, os.path.basename(filename)))
def mkdir_p(*args, **kwargs):
"""Like `mkdir`, but does not raise an exception if the
directory already exists.
"""
try:
return os.mkdir(*args, **kwargs)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
def build(source_dir, dist, dest=None):
pyproject = os.path.join(source_dir, 'pyproject.toml')
dest = os.path.join(source_dir, dest or 'dist')
mkdir_p(dest)
with open(pyproject) as f:
pyproject_data = pytoml.load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
_do_build(hooks, env, dist, dest)
parser = argparse.ArgumentParser()
parser.add_argument(
'source_dir',
help="A directory containing pyproject.toml",
)
parser.add_argument(
'--binary', '-b',
action='store_true',
default=False,
)
parser.add_argument(
'--source', '-s',
action='store_true',
default=False,
)
parser.add_argument(
'--out-dir', '-o',
help="Destination in which to save the builds relative to source dir",
)
def main(args):
# determine which dists to build
dists = list(filter(None, (
'sdist' if args.source or not args.binary else None,
'wheel' if args.binary or not args.source else None,
)))
for dist in dists:
build(args.source_dir, dist, args.out_dir)
if __name__ == '__main__':
main(parser.parse_args())
+202
View File
@@ -0,0 +1,202 @@
"""Check a project and backend by attempting to build using PEP 517 hooks.
"""
import argparse
import logging
import os
from os.path import isfile, join as pjoin
from pytoml import TomlError, load as toml_load
import shutil
from subprocess import CalledProcessError
import sys
import tarfile
from tempfile import mkdtemp
import zipfile
from .colorlog import enable_colourful_output
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
def check_build_sdist(hooks, build_sys_requires):
with BuildEnvironment() as env:
try:
env.pip_install(build_sys_requires)
log.info('Installed static build dependencies')
except CalledProcessError:
log.error('Failed to install static build dependencies')
return False
try:
reqs = hooks.get_requires_for_build_sdist({})
log.info('Got build requires: %s', reqs)
except Exception:
log.error('Failure in get_requires_for_build_sdist', exc_info=True)
return False
try:
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
except CalledProcessError:
log.error('Failed to install dynamic build dependencies')
return False
td = mkdtemp()
log.info('Trying to build sdist in %s', td)
try:
try:
filename = hooks.build_sdist(td, {})
log.info('build_sdist returned %r', filename)
except Exception:
log.info('Failure in build_sdist', exc_info=True)
return False
if not filename.endswith('.tar.gz'):
log.error(
"Filename %s doesn't have .tar.gz extension", filename)
return False
path = pjoin(td, filename)
if isfile(path):
log.info("Output file %s exists", path)
else:
log.error("Output file %s does not exist", path)
return False
if tarfile.is_tarfile(path):
log.info("Output file is a tar file")
else:
log.error("Output file is not a tar file")
return False
finally:
shutil.rmtree(td)
return True
def check_build_wheel(hooks, build_sys_requires):
with BuildEnvironment() as env:
try:
env.pip_install(build_sys_requires)
log.info('Installed static build dependencies')
except CalledProcessError:
log.error('Failed to install static build dependencies')
return False
try:
reqs = hooks.get_requires_for_build_wheel({})
log.info('Got build requires: %s', reqs)
except Exception:
log.error('Failure in get_requires_for_build_sdist', exc_info=True)
return False
try:
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
except CalledProcessError:
log.error('Failed to install dynamic build dependencies')
return False
td = mkdtemp()
log.info('Trying to build wheel in %s', td)
try:
try:
filename = hooks.build_wheel(td, {})
log.info('build_wheel returned %r', filename)
except Exception:
log.info('Failure in build_wheel', exc_info=True)
return False
if not filename.endswith('.whl'):
log.error("Filename %s doesn't have .whl extension", filename)
return False
path = pjoin(td, filename)
if isfile(path):
log.info("Output file %s exists", path)
else:
log.error("Output file %s does not exist", path)
return False
if zipfile.is_zipfile(path):
log.info("Output file is a zip file")
else:
log.error("Output file is not a zip file")
return False
finally:
shutil.rmtree(td)
return True
def check(source_dir):
pyproject = pjoin(source_dir, 'pyproject.toml')
if isfile(pyproject):
log.info('Found pyproject.toml')
else:
log.error('Missing pyproject.toml')
return False
try:
with open(pyproject) as f:
pyproject_data = toml_load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
log.info('Loaded pyproject.toml')
except (TomlError, KeyError):
log.error("Invalid pyproject.toml", exc_info=True)
return False
hooks = Pep517HookCaller(source_dir, backend)
sdist_ok = check_build_sdist(hooks, requires)
wheel_ok = check_build_wheel(hooks, requires)
if not sdist_ok:
log.warning('Sdist checks failed; scroll up to see')
if not wheel_ok:
log.warning('Wheel checks failed')
return sdist_ok
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument(
'source_dir',
help="A directory containing pyproject.toml")
args = ap.parse_args(argv)
enable_colourful_output()
ok = check(args.source_dir)
if ok:
print(ansi('Checks passed', 'green'))
else:
print(ansi('Checks failed', 'red'))
sys.exit(1)
ansi_codes = {
'reset': '\x1b[0m',
'bold': '\x1b[1m',
'red': '\x1b[31m',
'green': '\x1b[32m',
}
def ansi(s, attr):
if os.name != 'nt' and sys.stdout.isatty():
return ansi_codes[attr] + str(s) + ansi_codes['reset']
else:
return str(s)
if __name__ == '__main__':
main()
+115
View File
@@ -0,0 +1,115 @@
"""Nicer log formatting with colours.
Code copied from Tornado, Apache licensed.
"""
# Copyright 2012 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import sys
try:
import curses
except ImportError:
curses = None
def _stderr_supports_color():
color = False
if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
try:
curses.setupterm()
if curses.tigetnum("colors") > 0:
color = True
except Exception:
pass
return color
class LogFormatter(logging.Formatter):
"""Log formatter with colour support
"""
DEFAULT_COLORS = {
logging.INFO: 2, # Green
logging.WARNING: 3, # Yellow
logging.ERROR: 1, # Red
logging.CRITICAL: 1,
}
def __init__(self, color=True, datefmt=None):
r"""
:arg bool color: Enables color support.
:arg string fmt: Log message format.
It will be applied to the attributes dict of log records. The
text between ``%(color)s`` and ``%(end_color)s`` will be colored
depending on the level if color support is on.
:arg dict colors: color mappings from logging level to terminal color
code
:arg string datefmt: Datetime format.
Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
.. versionchanged:: 3.2
Added ``fmt`` and ``datefmt`` arguments.
"""
logging.Formatter.__init__(self, datefmt=datefmt)
self._colors = {}
if color and _stderr_supports_color():
# The curses module has some str/bytes confusion in
# python3. Until version 3.2.3, most methods return
# bytes, but only accept strings. In addition, we want to
# output these strings with the logging module, which
# works with unicode strings. The explicit calls to
# unicode() below are harmless in python2 but will do the
# right conversion in python 3.
fg_color = (curses.tigetstr("setaf") or
curses.tigetstr("setf") or "")
if (3, 0) < sys.version_info < (3, 2, 3):
fg_color = str(fg_color, "ascii")
for levelno, code in self.DEFAULT_COLORS.items():
self._colors[levelno] = str(
curses.tparm(fg_color, code), "ascii")
self._normal = str(curses.tigetstr("sgr0"), "ascii")
scr = curses.initscr()
self.termwidth = scr.getmaxyx()[1]
curses.endwin()
else:
self._normal = ''
# Default width is usually 80, but too wide is
# worse than too narrow
self.termwidth = 70
def formatMessage(self, record):
mlen = len(record.message)
right_text = '{initial}-{name}'.format(initial=record.levelname[0],
name=record.name)
if mlen + len(right_text) < self.termwidth:
space = ' ' * (self.termwidth - (mlen + len(right_text)))
else:
space = ' '
if record.levelno in self._colors:
start_color = self._colors[record.levelno]
end_color = self._normal
else:
start_color = end_color = ''
return record.message + space + start_color + right_text + end_color
def enable_colourful_output(level=logging.INFO):
handler = logging.StreamHandler()
handler.setFormatter(LogFormatter())
logging.root.addHandler(handler)
logging.root.setLevel(level)
+23
View File
@@ -0,0 +1,23 @@
"""Handle reading and writing JSON in UTF-8, on Python 3 and 2."""
import json
import sys
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# Python 2
def write_json(obj, path, **kwargs):
with open(path, 'wb') as f:
json.dump(obj, f, encoding='utf-8', **kwargs)
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
+158
View File
@@ -0,0 +1,158 @@
"""Build wheels/sdists by installing build deps to a temporary environment.
"""
import os
import logging
import pytoml
import shutil
from subprocess import check_call
import sys
from sysconfig import get_paths
from tempfile import mkdtemp
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
def _load_pyproject(source_dir):
with open(os.path.join(source_dir, 'pyproject.toml')) as f:
pyproject_data = pytoml.load(f)
buildsys = pyproject_data['build-system']
return buildsys['requires'], buildsys['build-backend']
class BuildEnvironment(object):
"""Context manager to install build deps in a simple temporary environment
Based on code I wrote for pip, which is MIT licensed.
"""
# Copyright (c) 2008-2016 The pip developers (see AUTHORS.txt file)
#
# 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.
path = None
def __init__(self, cleanup=True):
self._cleanup = cleanup
def __enter__(self):
self.path = mkdtemp(prefix='pep517-build-env-')
log.info('Temporary build environment: %s', self.path)
self.save_path = os.environ.get('PATH', None)
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
install_dirs = get_paths(install_scheme, vars={
'base': self.path,
'platbase': self.path,
})
scripts = install_dirs['scripts']
if self.save_path:
os.environ['PATH'] = scripts + os.pathsep + self.save_path
else:
os.environ['PATH'] = scripts + os.pathsep + os.defpath
if install_dirs['purelib'] == install_dirs['platlib']:
lib_dirs = install_dirs['purelib']
else:
lib_dirs = install_dirs['purelib'] + os.pathsep + \
install_dirs['platlib']
if self.save_pythonpath:
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
self.save_pythonpath
else:
os.environ['PYTHONPATH'] = lib_dirs
return self
def pip_install(self, reqs):
"""Install dependencies into this env by calling pip in a subprocess"""
if not reqs:
return
log.info('Calling pip to install %s', reqs)
check_call([
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
'--prefix', self.path] + list(reqs))
def __exit__(self, exc_type, exc_val, exc_tb):
needs_cleanup = (
self._cleanup and
self.path is not None and
os.path.isdir(self.path)
)
if needs_cleanup:
shutil.rmtree(self.path)
if self.save_path is None:
os.environ.pop('PATH', None)
else:
os.environ['PATH'] = self.save_path
if self.save_pythonpath is None:
os.environ.pop('PYTHONPATH', None)
else:
os.environ['PYTHONPATH'] = self.save_pythonpath
def build_wheel(source_dir, wheel_dir, config_settings=None):
"""Build a wheel from a source directory using PEP 517 hooks.
:param str source_dir: Source directory containing pyproject.toml
:param str wheel_dir: Target directory to create wheel in
:param dict config_settings: Options to pass to build backend
This is a blocking function which will run pip in a subprocess to install
build requirements.
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
reqs = hooks.get_requires_for_build_wheel(config_settings)
env.pip_install(reqs)
return hooks.build_wheel(wheel_dir, config_settings)
def build_sdist(source_dir, sdist_dir, config_settings=None):
"""Build an sdist from a source directory using PEP 517 hooks.
:param str source_dir: Source directory containing pyproject.toml
:param str sdist_dir: Target directory to place sdist in
:param dict config_settings: Options to pass to build backend
This is a blocking function which will run pip in a subprocess to install
build requirements.
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
reqs = hooks.get_requires_for_build_sdist(config_settings)
env.pip_install(reqs)
return hooks.build_sdist(sdist_dir, config_settings)
+163
View File
@@ -0,0 +1,163 @@
from contextlib import contextmanager
import os
from os.path import dirname, abspath, join as pjoin
import shutil
from subprocess import check_call
import sys
from tempfile import mkdtemp
from . import compat
_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py')
@contextmanager
def tempdir():
td = mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
class BackendUnavailable(Exception):
"""Will be raised if the backend cannot be imported in the hook process."""
class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't."""
def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
"""The default method of calling the wrapper subprocess."""
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
check_call(cmd, cwd=cwd, env=env)
class Pep517HookCaller(object):
"""A wrapper around a source directory to be built with a PEP 517 backend.
source_dir : The path to the source directory, containing pyproject.toml.
backend : The build backend spec, as per PEP 517, from pyproject.toml.
"""
def __init__(self, source_dir, build_backend):
self.source_dir = abspath(source_dir)
self.build_backend = build_backend
self._subprocess_runner = default_subprocess_runner
# TODO: Is this over-engineered? Maybe frontends only need to
# set this when creating the wrapper, not on every call.
@contextmanager
def subprocess_runner(self, runner):
prev = self._subprocess_runner
self._subprocess_runner = runner
yield
self._subprocess_runner = prev
def get_requires_for_build_wheel(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
["wheel >= 0.25", "setuptools"]
This does not include requirements specified in pyproject.toml.
It returns the result of calling the equivalently named hook in a
subprocess.
"""
return self._call_hook('get_requires_for_build_wheel', {
'config_settings': config_settings
})
def prepare_metadata_for_build_wheel(
self, metadata_directory, config_settings=None):
"""Prepare a *.dist-info folder with metadata for this project.
Returns the name of the newly created folder.
If the build backend defines a hook with this name, it will be called
in a subprocess. If not, the backend will be asked to build a wheel,
and the dist-info extracted from that.
"""
return self._call_hook('prepare_metadata_for_build_wheel', {
'metadata_directory': abspath(metadata_directory),
'config_settings': config_settings,
})
def build_wheel(
self, wheel_directory, config_settings=None,
metadata_directory=None):
"""Build a wheel from this project.
Returns the name of the newly created file.
In general, this will call the 'build_wheel' hook in the backend.
However, if that was previously called by
'prepare_metadata_for_build_wheel', and the same metadata_directory is
used, the previously built wheel will be copied to wheel_directory.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
return self._call_hook('build_wheel', {
'wheel_directory': abspath(wheel_directory),
'config_settings': config_settings,
'metadata_directory': metadata_directory,
})
def get_requires_for_build_sdist(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
["setuptools >= 26"]
This does not include requirements specified in pyproject.toml.
It returns the result of calling the equivalently named hook in a
subprocess.
"""
return self._call_hook('get_requires_for_build_sdist', {
'config_settings': config_settings
})
def build_sdist(self, sdist_directory, config_settings=None):
"""Build an sdist from this project.
Returns the name of the newly created file.
This calls the 'build_sdist' backend hook in a subprocess.
"""
return self._call_hook('build_sdist', {
'sdist_directory': abspath(sdist_directory),
'config_settings': config_settings,
})
def _call_hook(self, hook_name, kwargs):
# On Python 2, pytoml returns Unicode values (which is correct) but the
# environment passed to check_call needs to contain string values. We
# convert here by encoding using ASCII (the backend can only contain
# letters, digits and _, . and : characters, and will be used as a
# Python identifier, so non-ASCII content is wrong on Python 2 in
# any case).
if sys.version_info[0] == 2:
build_backend = self.build_backend.encode('ASCII')
else:
build_backend = self.build_backend
with tempdir() as td:
compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'),
indent=2)
# Run the hook in a subprocess
self._subprocess_runner(
[sys.executable, _in_proc_script, hook_name, td],
cwd=self.source_dir,
extra_environ={'PEP517_BUILD_BACKEND': build_backend}
)
data = compat.read_json(pjoin(td, 'output.json'))
if data.get('unsupported'):
raise UnsupportedOperation
if data.get('no_backend'):
raise BackendUnavailable
return data['return_val']

Some files were not shown because too many files have changed in this diff Show More