mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a bug which caused editable package resolution to sometimes fail with an unhelpful setuptools-related error message.
|
||||
@@ -0,0 +1 @@
|
||||
Dependency resolution now writes hashes for local and remote files to the lockfile.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a bug which prevented ``pipenv graph`` from correctly showing all dependencies when running from within ``pipenv shell``.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed resolution of direct-url dependencies in ``setup.py`` files to respect ``PEP-508`` style URL dependencies.
|
||||
@@ -0,0 +1 @@
|
||||
Added support for resolution of direct-url dependencies in ``setup.py`` files to respect ``PEP-508`` style URL dependencies.
|
||||
@@ -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``.
|
||||
@@ -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.
|
||||
@@ -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**.
|
||||
@@ -0,0 +1 @@
|
||||
Pipenv will now successfully recursively lock VCS sub-dependencies.
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a keyerror which could occur when locking VCS dependencies in some cases.
|
||||
@@ -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 @@
|
||||
Fix the wrong order of old and new hashes in message.
|
||||
Fixed the wrong order of old and new hashes in message.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Fix the issue that lock file can't be created when ``PIPENV_PIPFILE`` is not under working directory.
|
||||
+6
-3
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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", [])
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
__author__ = """pyup.io"""
|
||||
__email__ = 'support@pyup.io'
|
||||
__version__ = '1.8.4'
|
||||
__version__ = '1.8.5'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -18,7 +18,7 @@ from ._make import (
|
||||
)
|
||||
|
||||
|
||||
__version__ = "18.2.0"
|
||||
__version__ = "19.1.0"
|
||||
|
||||
__title__ = "attrs"
|
||||
__description__ = "Classes Without Boilerplate"
|
||||
|
||||
Vendored
+15
-12
@@ -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: ...
|
||||
|
||||
Vendored
+21
-25
@@ -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.
|
||||
|
||||
Vendored
+79
-27
@@ -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:
|
||||
|
||||
Vendored
+3
-3
@@ -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]: ...
|
||||
|
||||
Vendored
+113
-1
@@ -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)
|
||||
|
||||
Vendored
+10
@@ -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]: ...
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
Vendored
+2
-2
@@ -1,3 +1,3 @@
|
||||
from .core import where, old_where
|
||||
from .core import where
|
||||
|
||||
__version__ = "2018.10.15"
|
||||
__version__ = "2018.11.29"
|
||||
|
||||
Vendored
+242
@@ -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-----
|
||||
|
||||
Vendored
-17
@@ -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())
|
||||
|
||||
Vendored
-1
@@ -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.
|
||||
|
||||
|
||||
Vendored
+1
-2
@@ -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
@@ -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)
|
||||
|
||||
|
||||
|
||||
Vendored
-2
@@ -78,5 +78,3 @@ def wrap_stream(stream, convert, strip, autoreset, wrap):
|
||||
if wrapper.should_wrap():
|
||||
stream = wrapper.stream
|
||||
return stream
|
||||
|
||||
|
||||
|
||||
Vendored
+7
-11
@@ -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)
|
||||
|
||||
Vendored
+9
-2
@@ -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
|
||||
|
||||
Vendored
-5
@@ -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.
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
from .cursor import hide, show, HiddenCursor
|
||||
|
||||
__all__ = ["hide", "show", "HiddenCursor"]
|
||||
|
||||
Vendored
-57
@@ -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)
|
||||
Vendored
+5
@@ -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
|
||||
|
||||
Vendored
+54
@@ -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
|
||||
Vendored
+126
-88
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.9.1"
|
||||
__version__ = "0.10.1"
|
||||
|
||||
+1
-4
@@ -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
|
||||
|
||||
+104
-18
@@ -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,
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
__version__ = '2.7'
|
||||
__version__ = '2.8'
|
||||
|
||||
|
||||
+343
-317
File diff suppressed because it is too large
Load Diff
Vendored
-33
@@ -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.
|
||||
Vendored
+28
@@ -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.
|
||||
Vendored
+140
-118
@@ -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 <em>World</em>!')
|
||||
|
||||
>>> 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 <em>World</em>!')
|
||||
|
||||
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 & bar</em>')
|
||||
>>> strong = Markup("<strong>%(text)s</strong>")
|
||||
>>> strong % {'text': '<blink>hacker here</blink>'}
|
||||
Markup(u'<strong><blink>hacker here</blink></strong>')
|
||||
>>> Markup("<em>Hello</em> ") + "<foo>"
|
||||
Markup(u'<em>Hello</em> <foo>')
|
||||
>>> Markup('<em>%s</em>') % 'foo & bar'
|
||||
Markup('<em>foo & bar</em>')
|
||||
>>> Markup('<em>Hello</em> ') + '<foo>'
|
||||
Markup('<em>Hello</em> <foo>')
|
||||
"""
|
||||
|
||||
__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 » <em>About</em>").unescape()
|
||||
u'Main \xbb <em>About</em>'
|
||||
>>> Markup('Main » <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 » <em>About</em>").striptags()
|
||||
u'Main \xbb About'
|
||||
>>> Markup('Main »\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")
|
||||
|
||||
Vendored
+15
-8
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
Vendored
+45
-22
@@ -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('&', '&')
|
||||
.replace('>', '>')
|
||||
.replace('<', '<')
|
||||
.replace("'", ''')
|
||||
.replace('"', '"')
|
||||
if hasattr(s, "__html__"):
|
||||
return Markup(s.__html__())
|
||||
return Markup(
|
||||
text_type(s)
|
||||
.replace("&", "&")
|
||||
.replace(">", ">")
|
||||
.replace("<", "<")
|
||||
.replace("'", "'")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
|
||||
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('<User 1>')
|
||||
>>> escape(str(value))
|
||||
Markup('&lt;User 1&gt;')
|
||||
>>> escape(soft_unicode(value))
|
||||
Markup('<User 1>')
|
||||
"""
|
||||
if not isinstance(s, text_type):
|
||||
s = text_type(s)
|
||||
|
||||
Vendored
+199
-15
@@ -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(""");
|
||||
escaped_chars_repl['\''] = UNICHR("'");
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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())
|
||||
Vendored
+10
-4
@@ -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__
|
||||
|
||||
Vendored
+16
-4
@@ -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__",
|
||||
]
|
||||
|
||||
Vendored
+4
-3
@@ -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
@@ -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"
|
||||
|
||||
|
||||
Vendored
+42
-47
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+1
-7
@@ -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:
|
||||
|
||||
Vendored
+14
-35
@@ -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
|
||||
|
||||
Vendored
+74
-57
@@ -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
|
||||
|
||||
Vendored
+2
-2
@@ -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):
|
||||
|
||||
Vendored
+21
@@ -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.
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
"""Wrappers to build Python packages using PEP 517 hooks
|
||||
"""
|
||||
|
||||
__version__ = '0.5.0'
|
||||
Vendored
+207
@@ -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()
|
||||
Vendored
+108
@@ -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())
|
||||
Vendored
+202
@@ -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()
|
||||
Vendored
+115
@@ -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)
|
||||
Vendored
+23
@@ -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)
|
||||
Vendored
+158
@@ -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)
|
||||
Vendored
+163
@@ -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
Reference in New Issue
Block a user