Repository: sarugaku/requirementslib

Issue:
AST parsing of setup.py fails on non-addition type binOps
For specific cases in complex `setup.py` parsing, `ast_unparse_setup_py` fails due to its lack of adequate mapping of `ast.binOps` operators.

One good example is when parsing the `setup.py` that accompanies `PyYAML`:

```pycon
>>> ast_parse_setup_py("/tmp/pkgs/PyYAML-5.3/setup.py")
...
  File "/home/techalchemy/.virtualenvs/tempenv-74f7279776e1c/lib/python3.8/site-packages/requirementslib/models/setup_info.py", line 664, in visit_BinOp
    left = ast_unparse(node.left, initial_mapping=True)
  File "/home/techalchemy/.virtualenvs/tempenv-74f7279776e1c/lib/python3.8/site-packages/requirementslib/models/setup_info.py", line 693, in ast_unparse
    unparsed = [unparse(el) for el in item.elts]
  File "/home/techalchemy/.virtualenvs/tempenv-74f7279776e1c/lib/python3.8/site-packages/requirementslib/models/setup_info.py", line 693, in <listcomp>
    unparsed = [unparse(el) for el in item.elts]
  File "/home/techalchemy/.virtualenvs/tempenv-74f7279776e1c/lib/python3.8/site-packages/requirementslib/models/setup_info.py", line 695, in ast_unparse
    unparsed = tuple([unparse(el) for el in item.elts])
  File "/home/techalchemy/.virtualenvs/tempenv-74f7279776e1c/lib/python3.8/site-packages/requirementslib/models/setup_info.py", line 695, in <listcomp>
    unparsed = tuple([unparse(el) for el in item.elts])
  File "/home/techalchemy/.virtualenvs/tempenv-74f7279776e1c/lib/python3.8/site-packages/requirementslib/models/setup_info.py", line 794, in ast_unparse
    return unparsed
UnboundLocalError: local variable 'unparsed' referenced before assignment

local variable 'unparsed' referenced before assignment
```

This is currently a release blocker for pypa/pipenv#3369

Discussion:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f579267..dde3109 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/ambv/black
-    rev: 19.10b0
+    rev: stable
     hooks:
       - id: black
         args: ["--target-version=py27", "--target-version=py37"]
@@ -11,11 +11,18 @@ repos:
       #- id: flake8
 
   - repo: https://github.com/asottile/seed-isort-config
-    rev: v1.9.3
+    rev: v2.1.0
     hooks:
       - id: seed-isort-config
+        args: [
+          --application-directories=src/requirementslib,
+          --settings-path=./,
+          # --exclude=tests/.*\.py,
+          # --exclude=src/requirementslib/models/setup_info.py
+        ]
 
-  - repo: https://github.com/pre-commit/mirrors-isort
-    rev: v4.3.21
+  - repo: https://github.com/timothycrosley/isort
+    rev: 4.3.21
     hooks:
       - id: isort
+
diff --git a/Pipfile.lock b/Pipfile.lock
index 6f07b19..69e30bb 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -78,11 +78,11 @@
         },
         "babel": {
             "hashes": [
-                "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab",
-                "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"
+                "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
+                "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==2.7.0"
+            "version": "==2.8.0"
         },
         "backports.functools-lru-cache": {
             "hashes": [
@@ -97,15 +97,23 @@
                 "sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64",
                 "sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80"
             ],
-            "markers": "python_version < '3.3'",
+            "markers": "python_version == '2.7'",
             "version": "==1.0.0"
         },
+        "backports.tempfile": {
+            "hashes": [
+                "sha256:05aa50940946f05759696156a8c39be118169a0e0f94a49d0bb106503891ff54",
+                "sha256:1c648c452e8770d759bdc5a5e2431209be70d25484e1be24876cf2168722c762"
+            ],
+            "markers": "python_version < '3.5'",
+            "version": "==1.0"
+        },
         "backports.weakref": {
             "hashes": [
                 "sha256:81bc9b51c0abc58edc76aefbbc68c62a787918ffe943a37947e162c3f8e19e82",
                 "sha256:bc4170a29915f8b22c9e7c4939701859650f2eb84184aee80da329ac0b9825c2"
             ],
-            "markers": "python_version < '3.3'",
+            "markers": "python_version == '2.7'",
             "version": "==1.0.post1"
         },
         "black": {
@@ -235,10 +243,11 @@
         },
         "colorama": {
             "hashes": [
-                "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
-                "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
+                "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
+                "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
             ],
-            "version": "==0.4.1"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==0.4.3"
         },
         "configparser": {
             "hashes": [
@@ -258,38 +267,40 @@
         },
         "coverage": {
             "hashes": [
-                "sha256:2358e685d0253125da42a48396038d4c7b4cd1448c00bbc4bda0cb8c43c2a870",
-                "sha256:25017cf384eeed2e6caf72efd3ec4124e32a8b7a4387600499104387975400c7",
-                "sha256:2e2de9423ff8b14303a97eafddd16c479fbcc9a0b8b0be3b7c3843a3e0cf6d69",
-                "sha256:324ed908e4e40a6e2451056fe502470ad4e79495cb7a03ecab94e6309c3e117e",
-                "sha256:34f865a0cf6255b694a46e4383a7131c61ea72c5b4c4f81d20e522fb1e440b4b",
-                "sha256:3a2bcc464b60a18f1f7167b95b2773ede93bf3722bfa59e0802717f652b6cc25",
-                "sha256:48d70865266d649b6602e2ba94820d7972ef470d3b72a8fd41a3d17321feed3a",
-                "sha256:50cf23523ab3a724c6905d3b60f87fa8250d9bae3995e09f49f63effa2b54f15",
-                "sha256:54c84a68abd8c4c5b71878b35eb85321df41f3d144c78181867d5b026ec74994",
-                "sha256:5b59d661ee7f3200aedd7b71882b7927ea7ed522df75e3853f316a79ad872a2e",
-                "sha256:5ffb39624bc573177888a21fb301ccee46838c600b27d58c3e9dae495f44d34a",
-                "sha256:699b3072b7f0e69ed175a88fa8b2ec7eefc4f34d490c54ed9a52feff21a15fdc",
-                "sha256:79ef4a2bb862110bd585174e551a783bee5c3aa461734a2ac7429193be357589",
-                "sha256:8210a6f93c4a8c6d460b402e20e38399529b99200c3318542faf6a520c9b6a5c",
-                "sha256:8d30c10cfd0a6fdf0a2d5023de00ef7b329cd6ead2310c9e53eab79c209acb70",
-                "sha256:97ac79ff28f2cda6ac00a803ee582b965951755f61ab43377482bfba450b619a",
-                "sha256:9fe4aacacff9028ed167db108bf013510654f148d83c4857fed61d2ce0588bf2",
-                "sha256:a5b6395d5957d638f8b1870561607e3c39b1a236ea6cff9eafe5b9bb1db913f2",
-                "sha256:ab32c5fad6905986a7e34e3acf01180a69bb60c2aa7331815b46e51c776a1943",
-                "sha256:ad67f0cfdfecbd49b9da46a7e488e6dc32a69388740b85c36a4ef4b33082cbad",
-                "sha256:aedad67c30326a1af324f45833a40b97180664912deb29942459ddbe9fa0ce19",
-                "sha256:b077cd0e70f41366ac1f9d09275258fa1906758a5d4f31cacc18b10dfcf90784",
-                "sha256:b8ea210810d3c14aec7561f8fe0d3eec582d1088100aaa0bb8153d53d867d20f",
-                "sha256:bf572722326ce6704e863447a070039a827072b7179352570859be899b9e6551",
-                "sha256:c0df57e189dacd2606cae6386acf127d01d85b2bf49acd9a65543b5d6c359ddc",
-                "sha256:d523e75f2a8a0b4a6a8be1287c0e0e3a561b8832b05ddd987d4cd7c62f3ad3bc",
-                "sha256:e10593c60c5f0bfd8b241bf9f27ef2191a3005b73dde8ada0424f642543a1e59",
-                "sha256:e9128444c83bc260aea988bf1ca6278a33ba730955bf94720468c656b61353eb",
-                "sha256:f7162f2e3711f3a08a8a741f92e1f63afd58d0713177979f2cf9723dd50161cf"
+                "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
+                "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
+                "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
+                "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
+                "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
+                "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
+                "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
+                "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
+                "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
+                "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
+                "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
+                "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
+                "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
+                "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
+                "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
+                "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
+                "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
+                "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
+                "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
+                "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
+                "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
+                "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
+                "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
+                "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
+                "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
+                "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
+                "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
+                "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
+                "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
+                "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
+                "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'",
-            "version": "==5.0b1"
+            "version": "==5.0.3"
         },
         "cryptography": {
             "hashes": [
@@ -331,13 +342,19 @@
             ],
             "version": "==0.3.0"
         },
+        "docopt": {
+            "hashes": [
+                "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
+            ],
+            "version": "==0.6.2"
+        },
         "docutils": {
             "hashes": [
-                "sha256:7a6228589435302e421f5c473ce0180878b90f70227f7174cacde5efbd34275f",
-                "sha256:f1bad547016f945f7b35b28d8bead307821822ca3f8d4f87a1bd2ad1a8faab51"
+                "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
+                "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==0.16b0.dev0"
+            "version": "==0.16"
         },
         "entrypoints": {
             "hashes": [
@@ -349,13 +366,12 @@
         },
         "enum34": {
             "hashes": [
-                "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
-                "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
-                "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
-                "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
+                "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53",
+                "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328",
+                "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"
             ],
             "markers": "python_version < '3.4'",
-            "version": "==1.1.6"
+            "version": "==1.1.10"
         },
         "execnet": {
             "hashes": [
@@ -379,13 +395,6 @@
             ],
             "version": "==1.5.2"
         },
-        "first": {
-            "hashes": [
-                "sha256:8d8e46e115ea8ac652c76123c0865e3ff18372aef6f03c22809ceefcea9dec86",
-                "sha256:ff285b08c55f8c97ce4ea7012743af2495c9f1291785f163722bd36f6af6d3bf"
-            ],
-            "version": "==2.0.2"
-        },
         "flake8": {
             "hashes": [
                 "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
@@ -396,11 +405,11 @@
         },
         "flake8-bugbear": {
             "hashes": [
-                "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63",
-                "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162"
+                "sha256:d8c466ea79d5020cb20bf9f11cf349026e09517a42264f313d3f6fddb83e0571",
+                "sha256:ded4d282778969b5ab5530ceba7aa1a9f1b86fa7618fc96a19a1d512331640f8"
             ],
-            "markers": "python_version > '3.5'",
-            "version": "==20.1.4"
+            "markers": "python_version >= '3.5'",
+            "version": "==19.8.0"
         },
         "funcsigs": {
             "hashes": [
@@ -428,19 +437,19 @@
         },
         "hypothesis": {
             "hashes": [
-                "sha256:3870b151a25a36d94d1c9174404b506b1165e507904ec8a9ec049cca51329008",
-                "sha256:693fe3f72a4bbb67529c1873c1350c3eb0c028c323611dce9e7f3c341ddafbb1"
+                "sha256:3c4369a4b0a1348561048bcda5f1db951a1b8e2a514ea8e8c70d36e656bf6fa0",
+                "sha256:94f0910bc87e0ae8c098f4ada28dfdc381245e0c8079c674292b417dbde144b5"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==4.50.7"
+            "version": "==4.57.1"
         },
         "identify": {
             "hashes": [
-                "sha256:7782115794ec28b011702815d9f5e532244560cd2bf0789c4f09381d43befd90",
-                "sha256:9e7521e9abeaede4d2d1092a106e418c65ddf6b3182b43930bcb3c8cfb974488"
+                "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5",
+                "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.4.8"
+            "version": "==1.4.11"
         },
         "idna": {
             "hashes": [
@@ -452,34 +461,27 @@
         },
         "imagesize": {
             "hashes": [
-                "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
-                "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
+                "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
+                "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.1.0"
-        },
-        "importlab": {
-            "hashes": [
-                "sha256:d855350d19dc10a17aabd2fe6f4b428ff1a936071f692fbf686a73694d26a51c"
-            ],
-            "markers": "python_version >= '2.7.0'",
-            "version": "==0.5.1"
+            "version": "==1.2.0"
         },
         "importlib-metadata": {
             "hashes": [
-                "sha256:3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402",
-                "sha256:41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"
+                "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302",
+                "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"
             ],
             "markers": "python_version < '3.8'",
-            "version": "==1.2.0"
+            "version": "==1.5.0"
         },
         "importlib-resources": {
             "hashes": [
-                "sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b",
-                "sha256:d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"
+                "sha256:1dff36d42d94bd523eeb847c25c7dd327cb56686d74a26dfcc8d67c504922d59",
+                "sha256:7f0e1b2b5f3981e39c52da0f99b2955353c5a139c314994d1126c2551ace9bdf"
             ],
             "markers": "python_version < '3.7'",
-            "version": "==1.0.2"
+            "version": "==1.3.1"
         },
         "incremental": {
             "hashes": [
@@ -506,18 +508,18 @@
         },
         "jedi": {
             "hashes": [
-                "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27",
-                "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"
+                "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2",
+                "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==0.15.1"
+            "version": "==0.16.0"
         },
         "jeepney": {
             "hashes": [
                 "sha256:3479b861cc2b6407de5188695fa1a8d57e5072d7059322469b62628869b8e36e",
                 "sha256:d6c6b49683446d2407d2fe3acb7a368a77ff063f9182fe427da15d622adc24cf"
             ],
-            "markers": "sys_platform == 'linux'",
+            "markers": "python_version >= '3.6' and sys_platform == 'linux'",
             "version": "==0.4.3"
         },
         "jinja2": {
@@ -575,13 +577,16 @@
                 "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
                 "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
                 "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
+                "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
                 "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
                 "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
                 "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
                 "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
                 "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
                 "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
+                "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
                 "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
+                "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
                 "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
                 "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
                 "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
@@ -598,7 +603,9 @@
                 "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
                 "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
                 "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
-                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
+                "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
+                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
+                "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.1.1"
@@ -636,23 +643,23 @@
         },
         "mypy": {
             "hashes": [
-                "sha256:02d9bdd3398b636723ecb6c5cfe9773025a9ab7f34612c1cde5c7f2292e2d768",
-                "sha256:088f758a50af31cf8b42688118077292370c90c89232c783ba7979f39ea16646",
-                "sha256:28e9fbc96d13397a7ddb7fad7b14f373f91b5cff538e0772e77c270468df083c",
-                "sha256:30e123b24931f02c5d99307406658ac8f9cd6746f0d45a3dcac2fe5fbdd60939",
-                "sha256:3294821b5840d51a3cd7a2bb63b40fc3f901f6a3cfb3c6046570749c4c7ef279",
-                "sha256:41696a7d912ce16fdc7c141d87e8db5144d4be664a0c699a2b417d393994b0c2",
-                "sha256:4f42675fa278f3913340bb8c3371d191319704437758d7c4a8440346c293ecb2",
-                "sha256:54d205ccce6ed930a8a2ccf48404896d456e8b87812e491cb907a355b1a9c640",
-                "sha256:6992133c95a2847d309b4b0c899d7054adc60481df6f6b52bb7dee3d5fd157f7",
-                "sha256:6ecbd0e8e371333027abca0922b0c2c632a5b4739a0c61ffbd0733391e39144c",
-                "sha256:83fa87f556e60782c0fc3df1b37b7b4a840314ba1ac27f3e1a1e10cb37c89c17",
-                "sha256:c87ac7233c629f305602f563db07f5221950fe34fe30af072ac838fa85395f78",
-                "sha256:de9ec8dba773b78c49e7bec9a35c9b6fc5235682ad1fc2105752ae7c22f4b931",
-                "sha256:f385a0accf353ca1bca4bbf473b9d83ed18d923fdb809d3a70a385da23e25b6a"
+                "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2",
+                "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1",
+                "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164",
+                "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761",
+                "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce",
+                "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27",
+                "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754",
+                "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae",
+                "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9",
+                "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600",
+                "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65",
+                "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8",
+                "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913",
+                "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"
             ],
             "markers": "python_version >= '3.4'",
-            "version": "==0.750"
+            "version": "==0.770"
         },
         "mypy-extensions": {
             "hashes": [
@@ -691,6 +698,7 @@
                 "sha256:db31cef1eb979e4fe4539046cf04311e00f271f8687bde7dfb64d85f4e4d2b1e",
                 "sha256:fd72664f0e2506f2c8002f2ee67ddd50b87604fe8c1bd04d2108dfeacc82420d"
             ],
+            "markers": "python_version >= '3.6'",
             "version": "==1.9.0.post1"
         },
         "nodeenv": {
@@ -735,14 +743,15 @@
                 "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db",
                 "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"
             ],
-            "markers": "python_version < '3'",
+            "markers": "python_version < '3.6'",
             "version": "==2.3.5"
         },
         "pathspec": {
             "hashes": [
-                "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c"
+                "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
+                "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
             ],
-            "version": "==0.6.0"
+            "version": "==0.7.0"
         },
         "pathtools": {
             "hashes": [
@@ -759,8 +768,8 @@
         },
         "pip-shims": {
             "hashes": [
-                "sha256:5861da6f48e60b55d40b984795c63681e4db7ac576c1c3b05f4b54a9d508e3da",
-                "sha256:2b9a88ff0fd31e7d27a362d3e36e6e75d8fbc339c9c4367f4a97b72b22e6f4f4"
+                "sha256:2b9a88ff0fd31e7d27a362d3e36e6e75d8fbc339c9c4367f4a97b72b22e6f4f4",
+                "sha256:5861da6f48e60b55d40b984795c63681e4db7ac576c1c3b05f4b54a9d508e3da"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.5.1"
@@ -793,11 +802,11 @@
         },
         "pre-commit": {
             "hashes": [
-                "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6",
-                "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb"
+                "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850",
+                "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"
             ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2.1.1"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.21.0"
         },
         "prompt-toolkit": {
             "hashes": [
@@ -817,11 +826,11 @@
         },
         "py": {
             "hashes": [
-                "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
-                "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
+                "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
+                "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.8.0"
+            "version": "==1.8.1"
         },
         "pycodestyle": {
             "hashes": [
@@ -857,19 +866,19 @@
         },
         "pyparsing": {
             "hashes": [
-                "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f",
-                "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"
+                "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
+                "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
             ],
             "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==2.4.5"
+            "version": "==2.4.6"
         },
         "pytest": {
             "hashes": [
-                "sha256:5d0d20a9a66e39b5845ab14f8989f3463a7aa973700e6cdf02db69da9821e738",
-                "sha256:692d9351353ef709c1126266579edd4fd469dcf6b5f4f583050f72161d6f3592"
+                "sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339",
+                "sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==4.6.6"
+            "version": "==4.6.9"
         },
         "pytest-cov": {
             "hashes": [
@@ -889,31 +898,33 @@
         },
         "pytest-timeout": {
             "hashes": [
-                "sha256:4a30ba76837a32c7b7cd5c84ee9933fde4b9022b0cd20ea7d4a577c2a1649fb1",
-                "sha256:d49f618c6448c14168773b6cdda022764c63ea80d42274e3156787e8088d04c6"
+                "sha256:80faa19cd245a42b87a51699d640c00d937c02b749052bfca6bae8bdbe12c48e",
+                "sha256:95ca727d4a1dace6ec5f0534d2940b8417ff8b782f7eef0ea09240bdd94d95c2"
             ],
-            "version": "==1.3.3"
+            "version": "==1.3.4"
         },
         "pytest-xdist": {
             "hashes": [
-                "sha256:5d1b1d4461518a6023d56dab62fb63670d6f7537f23e2708459a557329accf48",
-                "sha256:a8569b027db70112b290911ce2ed732121876632fb3f40b1d39cd2f72f58b147"
+                "sha256:0f46020d3d9619e6d17a65b5b989c1ebbb58fc7b1da8fb126d70f4bac4dfeed1",
+                "sha256:7dc0d027d258cd0defc618fb97055fbd1002735ca7a6d17037018cf870e24011"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.30.0"
+            "version": "==1.31.0"
         },
-        "pytoml": {
+        "python-dateutil": {
             "hashes": [
-                "sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
+                "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
+                "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
             ],
-            "version": "==0.1.20"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.8.1"
         },
         "pytype": {
             "hashes": [
-                "sha256:53803f0f1fadf4923efc88a0bd0cfa4c242d2fa3f04421b6f8ab4d658ffd2b90"
+                "sha256:b22aaf5dd125134cad2cb3f45231c2fd4aa158b129bff81f880a0c6d6bb3c2a8"
             ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2020.2.6"
+            "markers": "python_version >= '3.4' and python_version < '3.8'",
+            "version": "==2020.2.20"
         },
         "pytz": {
             "hashes": [
@@ -924,19 +935,19 @@
         },
         "pyyaml": {
             "hashes": [
-                "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
-                "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
-                "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
-                "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
-                "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
-                "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
-                "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
-                "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
-                "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
-                "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
-                "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
+                "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
+                "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
+                "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
+                "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
+                "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
+                "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
+                "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
+                "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
+                "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
+                "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
+                "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
             ],
-            "version": "==5.2"
+            "version": "==5.3"
         },
         "readme-renderer": {
             "extras": [
@@ -948,32 +959,6 @@
             ],
             "version": "==24.0"
         },
-        "regex": {
-            "hashes": [
-                "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431",
-                "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242",
-                "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1",
-                "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d",
-                "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045",
-                "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b",
-                "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400",
-                "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa",
-                "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0",
-                "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69",
-                "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74",
-                "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb",
-                "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26",
-                "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5",
-                "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2",
-                "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce",
-                "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab",
-                "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e",
-                "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70",
-                "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc",
-                "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"
-            ],
-            "version": "==2020.2.20"
-        },
         "requests": {
             "hashes": [
                 "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
@@ -1008,11 +993,11 @@
         },
         "rope": {
             "hashes": [
-                "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969",
-                "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf",
-                "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"
+                "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203",
+                "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad",
+                "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"
             ],
-            "version": "==0.14.0"
+            "version": "==0.16.0"
         },
         "scandir": {
             "hashes": [
@@ -1033,19 +1018,19 @@
         },
         "secretstorage": {
             "hashes": [
-                "sha256:20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92",
-                "sha256:7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"
+                "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6",
+                "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b"
             ],
-            "markers": "sys_platform == 'linux'",
-            "version": "==3.1.1"
+            "markers": "python_version >= '3.6' and sys_platform == 'linux'",
+            "version": "==3.1.2"
         },
         "six": {
             "hashes": [
-                "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
-                "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
+                "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
+                "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.13.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.14.0"
         },
         "snowballstemmer": {
             "hashes": [
@@ -1054,6 +1039,13 @@
             ],
             "version": "==2.0.0"
         },
+        "sortedcontainers": {
+            "hashes": [
+                "sha256:974e9a32f56b17c1bac2aebd9dcf197f3eb9cd30553c5852a3187ad162e1a03a",
+                "sha256:d9e96492dd51fae31e60837736b38fe42a187b5404c16606ff7ee7cd582d4c60"
+            ],
+            "version": "==2.1.0"
+        },
         "sphinx": {
             "hashes": [
                 "sha256:9f3e17c64b34afc653d7c5ec95766e03043cc6d80b0de224f59b6b6e19d37c3c",
@@ -1070,54 +1062,6 @@
             "index": "pypi",
             "version": "==0.4.3"
         },
-        "sphinxcontrib-applehelp": {
-            "hashes": [
-                "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897",
-                "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.0.1"
-        },
-        "sphinxcontrib-devhelp": {
-            "hashes": [
-                "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34",
-                "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.0.1"
-        },
-        "sphinxcontrib-htmlhelp": {
-            "hashes": [
-                "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422",
-                "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.0.2"
-        },
-        "sphinxcontrib-jsmath": {
-            "hashes": [
-                "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
-                "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.0.1"
-        },
-        "sphinxcontrib-qthelp": {
-            "hashes": [
-                "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20",
-                "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.0.2"
-        },
-        "sphinxcontrib-serializinghtml": {
-            "hashes": [
-                "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227",
-                "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.1.3"
-        },
         "sphinxcontrib-websupport": {
             "hashes": [
                 "sha256:1501befb0fdf1d1c29a800fdbf4ef5dc5369377300ddbdd16d2cd40e54c6eefc",
@@ -1150,10 +1094,10 @@
         },
         "towncrier": {
             "hashes": [
-                "sha256:d0c49060522e4b45d02c8ffe0b63e5ffa660830f681446945212e5c66314228b",
-                "sha256:f0375efce604977b4098c18dc805d5ce0de59b232db8374cb3ca98b078b5afae"
+                "sha256:48251a1ae66d2cf7e6fa5552016386831b3e12bb3b2d08eb70374508c17a8196",
+                "sha256:de19da8b8cb44f18ea7ed3a3823087d2af8fcf497151bb9fd1e1b092ff56ed8d"
             ],
-            "version": "==19.9.0rc1"
+            "version": "==19.2.0"
         },
         "tqdm": {
             "hashes": [
@@ -1173,29 +1117,30 @@
         },
         "typed-ast": {
             "hashes": [
-                "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
-                "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e",
-                "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e",
-                "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0",
-                "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c",
-                "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47",
-                "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631",
-                "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4",
-                "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34",
-                "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b",
-                "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2",
-                "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e",
-                "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a",
-                "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233",
-                "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1",
-                "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36",
-                "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d",
-                "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a",
-                "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
-                "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
+                "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
+                "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
+                "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
+                "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
+                "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
+                "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
+                "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
+                "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
+                "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
+                "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
+                "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
+                "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
+                "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
+                "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
+                "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
+                "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
+                "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
+                "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
+                "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
+                "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
+                "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
             ],
             "markers": "python_version >= '3.4'",
-            "version": "==1.4.0"
+            "version": "==1.4.1"
         },
         "typing": {
             "hashes": [
@@ -1217,11 +1162,11 @@
         },
         "urllib3": {
             "hashes": [
-                "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",
-                "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"
+                "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
+                "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'",
-            "version": "==1.25.7"
+            "version": "==1.25.8"
         },
         "virtualenv": {
             "hashes": [
@@ -1233,11 +1178,11 @@
         },
         "vistir": {
             "hashes": [
-                "sha256:2166e3148a67c438c9e3edbba0cde153d42dec6e3bf5d8f4624feb27686c0990",
-                "sha256:3a0529b4b6c2e842fd19b5ceaa95b6c9201321314825c110406d4af3331a0709"
+                "sha256:33f8e905d40a77276b3d5310c8b57c1479a4e46930042b4894fcf7ed60ad76c4",
+                "sha256:e47afdec8baf35032a8d17116765f751ecd2f2146d47e5af457c5de1fe5a334e"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==0.4.3"
+            "version": "==0.5.0"
         },
         "vulture": {
             "hashes": [
@@ -1255,10 +1200,10 @@
         },
         "wcwidth": {
             "hashes": [
-                "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
-                "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
+                "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
+                "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
             ],
-            "version": "==0.1.7"
+            "version": "==0.1.8"
         },
         "webencodings": {
             "hashes": [
@@ -1277,11 +1222,11 @@
         },
         "zipp": {
             "hashes": [
-                "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
-                "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
+                "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1",
+                "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"
             ],
-            "markers": "python_version >= '2.7'",
-            "version": "==0.6.0"
+            "markers": "python_version < '3.8'",
+            "version": "==1.2.0"
         }
     }
 }
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index e7bb746..3b01a0c 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -43,7 +43,7 @@ jobs:
 
 - job: TestWindows
   pool:
-    vmImage: windows-2019
+    vmImage: windows-latest
   strategy:
     matrix:
       Python27:
@@ -54,13 +54,15 @@ jobs:
         python.version: '3.6'
       Python37:
         python.version: '3.7'
+      Python38:
+        python.version: '3.8'
     maxParallel: 4
   steps:
   - template: .azure-pipelines/templates/run-tests.yml
 
 - job: TestMacOS
   pool:
-    vmImage: macOS-10.13
+    vmImage: macOS-latest
   strategy:
     matrix:
       Python27:
@@ -71,6 +73,8 @@ jobs:
         python.version: '3.6'
       Python37:
         python.version: '3.7'
+      Python38:
+        python.version: '3.8'
     maxParallel: 4
   steps:
   - template: .azure-pipelines/templates/run-tests.yml
diff --git a/news/204.bugfix.rst b/news/204.bugfix.rst
new file mode 100644
index 0000000..f20d30b
--- /dev/null
+++ b/news/204.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed an issue in binary operator mapping in the ``ast_parse_setup_py`` functionality of the dependency parser which could cause dependency resolution to fail.
diff --git a/news/206.bugfix.rst b/news/206.bugfix.rst
new file mode 100644
index 0000000..9795814
--- /dev/null
+++ b/news/206.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed an issue which caused mappings of binary operators to fail to evaluate when parsing ``setup.py`` files.
diff --git a/news/207.bugfix.rst b/news/207.bugfix.rst
new file mode 100644
index 0000000..8f2ca99
--- /dev/null
+++ b/news/207.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed mapping and evaluation of boolean operators and comparisons when evaluating ``setup.py`` files with AST parser to discover dependencies.
diff --git a/setup.cfg b/setup.cfg
index 2b229de..c2f8551 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -50,18 +50,17 @@ install_requires =
     attrs>=18.2
     cached_property
     distlib>=0.2.8
-    first
     orderedmultidict
     packaging>=19.0
     pep517>=0.5.0
     pip-shims>=0.3.2
     plette[validation]
+    python-dateutil
     requests
     scandir;python_version<"3.5"
     setuptools>=40.8
     six>=1.11.0
     tomlkit>=0.5.3
-    typing;python_version<"3.5"
     vistir>=0.3.1
 
 [options.extras_require]
@@ -89,6 +88,7 @@ tests =
     coverage
     hypothesis
 typing =
+    typing;python_version<"3.5"
     mypy;python_version>="3.4"
     mypy-extensions;python_version>="3.4"
     mypytools;python_version>="3.4"
@@ -102,8 +102,8 @@ universal = 1
 
 [tool:pytest]
 strict = true
-plugins = flake8 cov timeout
-addopts = -ra --cov -n auto --timeout 300
+plugins = flake8 cov xdist timeout
+addopts = -ra --cov=requirementslib -n auto --timeout 300
 testpaths = tests/
 norecursedirs = .* build dist news tasks docs tests/artifacts
 flake8-ignore =
@@ -127,7 +127,7 @@ not_skip = __init__.py
 line_length = 90
 indent = '    '
 multi_line_output = 3
-known_third_party = appdirs,attr,cached_property,distlib,environ,first,hypothesis,invoke,orderedmultidict,packaging,parver,pep517,pip_shims,pkg_resources,plette,pyparsing,pytest,requests,setuptools,six,tomlkit,towncrier,urllib3,vistir
+known_third_party = appdirs,attr,cached_property,distlib,environ,hypothesis,invoke,orderedmultidict,packaging,parver,pep517,pip_shims,pkg_resources,plette,pyparsing,pytest,requests,setuptools,six,tomlkit,towncrier,urllib3,vistir
 known_first_party = requirementslib,tests
 combine_as_imports=True
 include_trailing_comma = True
diff --git a/src/requirementslib/models/dependencies.py b/src/requirementslib/models/dependencies.py
index 5e69b13..78c78ac 100644
--- a/src/requirementslib/models/dependencies.py
+++ b/src/requirementslib/models/dependencies.py
@@ -11,7 +11,6 @@ import packaging.markers
 import packaging.version
 import pip_shims.shims
 import requests
-from first import first
 from packaging.utils import canonicalize_name
 from vistir.compat import JSONDecodeError, fs_str
 from vistir.contextmanagers import cd, temp_environ
@@ -20,6 +19,7 @@ from vistir.path import create_tracked_tempdir
 from ..environment import MYPY_RUNNING
 from ..utils import _ensure_dir, prepare_pip_source_args
 from .cache import CACHE_DIR, DependencyCache
+from .setup_info import SetupInfo
 from .utils import (
     clean_requires_python,
     fix_requires_python_marker,
@@ -144,9 +144,9 @@ class AbstractDependency(object):
         :rtype: set(str)
         """
 
-        if len(self.candidates) == 1 and first(self.candidates).editable:
+        if len(self.candidates) == 1 and next(iter(self.candidates)).editable:
             return self
-        elif len(other.candidates) == 1 and first(other.candidates).editable:
+        elif len(other.candidates) == 1 and next(iter(other.candidates)).editable:
             return other
         return self.version_set & other.version_set
 
@@ -163,9 +163,9 @@ class AbstractDependency(object):
 
         from .requirements import Requirement
 
-        if len(self.candidates) == 1 and first(self.candidates).editable:
+        if len(self.candidates) == 1 and next(iter(self.candidates)).editable:
             return self
-        elif len(other.candidates) == 1 and first(other.candidates).editable:
+        elif len(other.candidates) == 1 and next(iter(other.candidates)).editable:
             return other
         new_specifiers = self.specifiers & other.specifiers
         markers = set(self.markers) if self.markers else set()
@@ -480,90 +480,19 @@ def get_dependencies_from_index(dep, sources=None, pip_options=None, wheel_cache
     if not wheel_cache:
         wheel_cache = WHEEL_CACHE
     dep.is_direct = True
-    reqset = pip_shims.shims.RequirementSet()
-    reqset.add_requirement(dep)
     requirements = None
     setup_requires = {}
-    with temp_environ(), start_resolver(
-        finder=finder, session=session, wheel_cache=wheel_cache
-    ) as resolver:
+    with temp_environ():
         os.environ["PIP_EXISTS_ACTION"] = "i"
-        dist = None
         if dep.editable and not dep.prepared and not dep.req:
-            with cd(dep.setup_py_dir):
-                from setuptools.dist import distutils
-
-                try:
-                    dist = distutils.core.run_setup(dep.setup_py)
-                except (ImportError, TypeError, AttributeError):
-                    dist = None
-                else:
-                    setup_requires[dist.get_name()] = dist.setup_requires
-                if not dist:
-                    try:
-                        dist = dep.get_dist()
-                    except (TypeError, ValueError, AttributeError):
-                        pass
-                    else:
-                        setup_requires[dist.get_name()] = dist.setup_requires
-        resolver.require_hashes = False
-        try:
-            results = resolver._resolve_one(reqset, dep)
-        except Exception:
-            # FIXME: Needs to bubble the exception somehow to the user.
-            results = []
-        finally:
-            try:
-                wheel_cache.cleanup()
-            except AttributeError:
-                pass
-        resolver_requires_python = getattr(resolver, "requires_python", None)
-        requires_python = getattr(reqset, "requires_python", resolver_requires_python)
-        if requires_python:
-            add_marker = fix_requires_python_marker(requires_python)
-            reqset.remove(dep)
-            if dep.req.marker:
-                dep.req.marker._markers.extend(["and"].extend(add_marker._markers))
-            else:
-                dep.req.marker = add_marker
-            reqset.add(dep)
-        requirements = set()
-        for r in results:
-            if requires_python:
-                if r.req.marker:
-                    r.req.marker._markers.extend(["and"].extend(add_marker._markers))
-                else:
-                    r.req.marker = add_marker
-            requirements.add(format_requirement(r))
-        for section in setup_requires:
-            python_version = section
-            not_python = not is_python(section)
-
-            # This is for cleaning up :extras: formatted markers
-            # by adding them to the results of the resolver
-            # since any such extra would have been returned as a result anyway
-            for value in setup_requires[section]:
-
-                # This is a marker.
-                if is_python(section):
-                    python_version = value[1:-1]
-                else:
-                    not_python = True
-
-                if ":" not in value and not_python:
-                    try:
-                        requirement_str = "{0}{1}".format(value, python_version).replace(
-                            ":", ";"
-                        )
-                        requirements.add(
-                            format_requirement(
-                                make_install_requirement(requirement_str).ireq
-                            )
-                        )
-                    # Anything could go wrong here -- can't be too careful.
-                    except Exception:
-                        pass
-
+            setup_info = SetupInfo.from_ireq(dep)
+            results = setup_info.get_info()
+            setup_requires.update(results["setup_requires"])
+            requirements = set(results["requires"].values())
+        else:
+            results = pip_shims.shims.resolve(dep)
+            requirements = [v for v in results.values() if v.name != dep.name]
+        requirements = set([format_requirement(r) for r in requirements])
     if not dep.editable and is_pinned_requirement(dep) and requirements is not None:
         DEPENDENCY_CACHE[dep] = list(requirements)
     return requirements
@@ -694,10 +623,10 @@ def get_grouped_dependencies(constraints):
     # then we take the loose match (which _is_ flexible) and start moving backwards in
     # versions by popping them off of a stack and checking for the conflicting package
     for _, ireqs in full_groupby(constraints, key=key_from_ireq):
-        ireqs = list(ireqs)
-        editable_ireq = first(ireqs, key=lambda ireq: ireq.editable)
+        ireqs = sorted(ireqs, key=lambda ireq: ireq.editable)
+        editable_ireq = next(iter(ireq for ireq in ireqs if ireq.editable), None)
         if editable_ireq:
-            yield editable_ireq  # ignore all the other specs: the editable one is the one that counts
+            yield editable_ireq  # only the editable match mattters, ignore all others
             continue
         ireqs = iter(ireqs)
         # deepcopy the accumulator so as to not modify the self.our_constraints invariant
diff --git a/src/requirementslib/models/markers.py b/src/requirementslib/models/markers.py
index c7649b7..8dda049 100644
--- a/src/requirementslib/models/markers.py
+++ b/src/requirementslib/models/markers.py
@@ -11,9 +11,9 @@ from packaging.specifiers import Specifier, SpecifierSet
 from vistir.compat import Mapping, Set, lru_cache
 from vistir.misc import dedup
 
-from .utils import filter_none, validate_markers
 from ..environment import MYPY_RUNNING
 from ..exceptions import RequirementError
+from .utils import filter_none, validate_markers
 
 from six.moves import reduce  # isort:skip
 
@@ -24,7 +24,8 @@ if MYPY_RUNNING:
     STRING_TYPE = Union[str, bytes, Text]
 
 
-MAX_VERSIONS = {2: 7, 3: 10}
+MAX_VERSIONS = {2: 7, 3: 11, 4: 0}
+DEPRECATED_VERSIONS = ["3.0", "3.1", "3.2", "3.3"]
 
 
 def is_instance(item, cls):
@@ -147,9 +148,8 @@ def _format_pyspec(specifier):
     version = getattr(specifier, "version", specifier).rstrip()
     if version and version.endswith("*"):
         if version.endswith(".*"):
-            version = version.rstrip(".*")
-        else:
-            version = version.rstrip("*")
+            version = version[:-2]
+        version = version.rstrip("*")
         specifier = Specifier("{0}{1}".format(specifier.operator, version))
     try:
         op = REPLACE_RANGES[specifier.operator]
@@ -227,10 +227,10 @@ def normalize_specifier_set(specs):
         return {_format_pyspec(spec) for spec in specs}
     spec_list = []
     for spec in specs.split(","):
+        spec = spec.strip()
         if spec.endswith(".*"):
-            spec = spec.rstrip(".*")
-        elif spec.endswith("*"):
-            spec = spec.rstrip("*")
+            spec = spec[:-2]
+        spec = spec.rstrip("*")
         spec_list.append(spec)
     return normalize_specifier_set(SpecifierSet(",".join(spec_list)))
 
@@ -528,34 +528,63 @@ def contains_pyversion(marker):
     return _markers_contains_pyversion(marker._markers)
 
 
+def _split_specifierset_str(specset_str, prefix="=="):
+    # type: (str, str) -> Set[Specifier]
+    """
+    Take a specifierset string and split it into a list to join for specifier sets
+
+    :param str specset_str: A string containing python versions, often comma separated
+    :param str prefix: A prefix to use when generating the specifier set
+    :return: A list of :class:`Specifier` instances generated with the provided prefix
+    :rtype: Set[Specifier]
+    """
+    specifiers = set()
+    if "," not in specset_str and " " in specset_str:
+        values = [v.strip() for v in specset_str.split()]
+    else:
+        values = [v.strip() for v in specset_str.split(",")]
+    if prefix == "!=" and any(v in values for v in DEPRECATED_VERSIONS):
+        values = DEPRECATED_VERSIONS[:]
+    for value in sorted(values):
+        specifiers.add(Specifier("{0}{1}".format(prefix, value)))
+    return specifiers
+
+
+def _get_specifiers_from_markers(marker_item):
+    """
+    Given a marker item, get specifiers from the version marker
+
+    :param :class:`~packaging.markers.Marker` marker_sequence: A marker describing a version constraint
+    :return: A set of specifiers corresponding to the marker constraint
+    :rtype: Set[Specifier]
+    """
+    specifiers = set()
+    if isinstance(marker_item, tuple):
+        variable, op, value = marker_item
+        if variable.value != "python_version":
+            return specifiers
+        if op.value == "in":
+            specifiers.update(_split_specifierset_str(value.value, prefix="=="))
+        elif op.value == "not in":
+            specifiers.update(_split_specifierset_str(value.value, prefix="!="))
+        else:
+            specifiers.add(Specifier("{0}{1}".format(op.value, value.value)))
+    elif isinstance(marker_item, list):
+        parts = get_specset(marker_item)
+        if parts:
+            specifiers.update(parts)
+    return specifiers
+
+
 def get_specset(marker_list):
     # type: (List) -> Optional[SpecifierSet]
     specset = set()
     _last_str = "and"
     for marker_parts in marker_list:
-        if isinstance(marker_parts, tuple):
-            variable, op, value = marker_parts
-            if variable.value != "python_version":
-                continue
-            if op.value == "in":
-                values = [v.strip() for v in value.value.split(",")]
-                specset.update(Specifier("=={0}".format(v)) for v in values)
-            elif op.value == "not in":
-                values = [v.strip() for v in value.value.split(",")]
-                bad_versions = ["3.0", "3.1", "3.2", "3.3"]
-                if len(values) >= 2 and any(v in values for v in bad_versions):
-                    values = bad_versions
-                specset.update(
-                    Specifier("!={0}".format(v.strip())) for v in sorted(bad_versions)
-                )
-            else:
-                specset.add(Specifier("{0}{1}".format(op.value, value.value)))
-        elif isinstance(marker_parts, list):
-            parts = get_specset(marker_parts)
-            if parts:
-                specset.update(parts)
-        elif isinstance(marker_parts, str):
-            _last_str = marker_parts
+        if isinstance(marker_parts, str):
+            _last_str = marker_parts  # noqa
+        else:
+            specset.update(_get_specifiers_from_markers(marker_parts))
     specifiers = SpecifierSet()
     specifiers._specs = frozenset(specset)
     return specifiers
diff --git a/src/requirementslib/models/requirements.py b/src/requirementslib/models/requirements.py
index 1ec47f7..0537ca0 100644
--- a/src/requirementslib/models/requirements.py
+++ b/src/requirementslib/models/requirements.py
@@ -15,7 +15,6 @@ import pip_shims
 import six
 import vistir
 from cached_property import cached_property
-from first import first
 from packaging.markers import Marker
 from packaging.requirements import Requirement as PackagingRequirement
 from packaging.specifiers import (
@@ -793,27 +792,25 @@ class Line(object):
 
     def get_setup_info(self):
         # type: () -> SetupInfo
-        setup_info = SetupInfo.from_ireq(self.ireq)
-        if not setup_info.name:
-            setup_info.get_info()
+        setup_info = None
+        with pip_shims.shims.global_tempdir_manager():
+            setup_info = SetupInfo.from_ireq(self.ireq)
+            if not setup_info.name:
+                setup_info.get_info()
         return setup_info
 
     @property
     def setup_info(self):
         # type: () -> Optional[SetupInfo]
-        if self._setup_info is None and not self.is_named and not self.is_wheel:
-            if self._setup_info:
-                if not self._setup_info.name:
-                    self._setup_info.get_info()
-            else:
-                # make two attempts at this before failing to allow for stale data
+        if not self._setup_info and not self.is_named and not self.is_wheel:
+            # make two attempts at this before failing to allow for stale data
+            try:
+                self.setup_info = self.get_setup_info()
+            except FileNotFoundError:
                 try:
                     self.setup_info = self.get_setup_info()
                 except FileNotFoundError:
-                    try:
-                        self.setup_info = self.get_setup_info()
-                    except FileNotFoundError:
-                        raise
+                    raise
         return self._setup_info
 
     @setup_info.setter
@@ -890,7 +887,7 @@ class Line(object):
         wheel_kwargs = self.wheel_kwargs.copy()
         wheel_kwargs["src_dir"] = repo.checkout_directory
         ireq.ensure_has_source_dir(wheel_kwargs["src_dir"])
-        with temp_path():
+        with pip_shims.shims.global_tempdir_manager(), temp_path():
             sys.path = [repo.checkout_directory, "", ".", get_python_lib(plat_specific=0)]
             setupinfo = SetupInfo.create(
                 repo.checkout_directory,
@@ -1065,7 +1062,7 @@ class Line(object):
         # type: () -> "Line"
         if self._name is None:
             self.parse_name()
-            if not self._name and not self.is_vcs and not self.is_named:
+            if not any([self._name, self.is_vcs, self.is_named]):
                 if self.setup_info and self.setup_info.name:
                     self._name = self.setup_info.name
         name, extras, url = self.requirement_info
@@ -1562,16 +1559,18 @@ class FileRequirement(object):
                     self._parsed_line._setup_info
                     and not self._parsed_line._setup_info.name
                 ):
-                    self._parsed_line._setup_info.get_info()
+                    with pip_shims.shims.global_tempdir_manager():
+                        self._parsed_line._setup_info.get_info()
                 self._setup_info = self.parsed_line._setup_info
             elif self.parsed_line and (
                 self.parsed_line.ireq and not self.parsed_line.is_wheel
             ):
-                self._setup_info = SetupInfo.from_ireq(self.parsed_line.ireq)
+                with pip_shims.shims.global_tempdir_manager():
+                    self._setup_info = SetupInfo.from_ireq(self.parsed_line.ireq)
             else:
                 if self.link and not self.link.is_wheel:
                     self._setup_info = Line(self.line_part).setup_info
-                    if self._setup_info:
+                    with pip_shims.shims.global_tempdir_manager():
                         self._setup_info.get_info()
         return self._setup_info
 
@@ -1958,20 +1957,23 @@ class VCSRequirement(FileRequirement):
     def setup_info(self):
         if self._parsed_line and self._parsed_line.setup_info:
             if not self._parsed_line.setup_info.name:
-                self._parsed_line._setup_info.get_info()
+                with pip_shims.shims.global_tempdir_manager():
+                    self._parsed_line._setup_info.get_info()
             return self._parsed_line.setup_info
         if self._repo:
             from .setup_info import SetupInfo
 
-            self._setup_info = SetupInfo.from_ireq(
-                Line(self._repo.checkout_directory).ireq
-            )
-            self._setup_info.get_info()
+            with pip_shims.shims.global_tempdir_manager():
+                self._setup_info = SetupInfo.from_ireq(
+                    Line(self._repo.checkout_directory).ireq
+                )
+                self._setup_info.get_info()
             return self._setup_info
         ireq = self.parsed_line.ireq
         from .setup_info import SetupInfo
 
-        self._setup_info = SetupInfo.from_ireq(ireq)
+        with pip_shims.shims.global_tempdir_manager():
+            self._setup_info = SetupInfo.from_ireq(ireq)
         return self._setup_info
 
     @setup_info.setter
@@ -2275,7 +2277,7 @@ class VCSRequirement(FileRequirement):
         alt_type = ""  # type: Optional[STRING_TYPE]
         vcs_value = ""  # type: STRING_TYPE
         if src_keys:
-            chosen_key = first(src_keys)
+            chosen_key = next(iter(src_keys))
             vcs_type = pipfile.pop("vcs")
             if chosen_key in pipfile:
                 vcs_value = pipfile[chosen_key]
@@ -2565,7 +2567,8 @@ class Requirement(object):
         if self.req is not None and (
             not isinstance(self.req, NamedRequirement) and self.req.is_local
         ):
-            setup_info = self.run_requires()
+            with pip_shims.shims.global_tempdir_manager():
+                setup_info = self.run_requires()
             build_backend = setup_info.get("build_backend")
             return build_backend
         return "setuptools.build_meta"
@@ -2677,7 +2680,7 @@ class Requirement(object):
         if hasattr(pipfile, "keys"):
             _pipfile = dict(pipfile).copy()
         _pipfile["version"] = get_version(pipfile)
-        vcs = first([vcs for vcs in VCS_LIST if vcs in _pipfile])
+        vcs = next(iter([vcs for vcs in VCS_LIST if vcs in _pipfile]), None)
         if vcs:
             _pipfile["vcs"] = vcs
             r = VCSRequirement.from_pipfile(name, pipfile)
@@ -2959,10 +2962,11 @@ class Requirement(object):
                 from .dependencies import get_finder
 
                 finder = get_finder(sources=sources)
-            info = SetupInfo.from_requirement(self, finder=finder)
-            if info is None:
-                return {}
-            info_dict = info.get_info()
+            with pip_shims.shims.global_tempdir_manager():
+                info = SetupInfo.from_requirement(self, finder=finder)
+                if info is None:
+                    return {}
+                info_dict = info.get_info()
             if self.req and not self.req.setup_info:
                 self.req._setup_info = info
         if self.req._has_hashed_name and info_dict.get("name"):
diff --git a/src/requirementslib/models/setup_info.py b/src/requirementslib/models/setup_info.py
index 47846e5..4ccff88 100644
--- a/src/requirementslib/models/setup_info.py
+++ b/src/requirementslib/models/setup_info.py
@@ -6,6 +6,7 @@ import atexit
 import contextlib
 import importlib
 import io
+import operator
 import os
 import shutil
 import sys
@@ -47,6 +48,10 @@ except ImportError:
     import distutils
     from distutils.core import Distribution
 
+try:
+    from contextlib import ExitStack
+except ImportError:
+    from contextlib2 import ExitStack
 
 try:
     from os import scandir
@@ -70,6 +75,7 @@ if MYPY_RUNNING:
         AnyStr,
         Sequence,
     )
+    import requests
     from pip_shims.shims import InstallRequirement, PackageFinder
     from pkg_resources import (
         PathMetadata,
@@ -281,8 +287,11 @@ def get_extras_from_setupcfg(parser):
     return extras
 
 
-def parse_setup_cfg(setup_cfg_contents, base_dir):
-    # type: (S, S) -> Dict[S, Union[S, None, Set[BaseRequirement], List[S], Dict[STRING_TYPE, Tuple[BaseRequirement]]]]
+def parse_setup_cfg(
+    setup_cfg_contents,  # type: S
+    base_dir,  # type: S
+):
+    # type: (...) -> Dict[S, Union[S, None, Set[BaseRequirement], List[S], Dict[STRING_TYPE, Tuple[BaseRequirement]]]]
     default_opts = {
         "metadata": {"name": "", "version": ""},
         "options": {
@@ -633,6 +642,41 @@ def get_metadata_from_dist(dist):
     }
 
 
+AST_BINOP_MAP = dict(
+    (
+        (ast.Add, operator.add),
+        (ast.Sub, operator.sub),
+        (ast.Mult, operator.mul),
+        (ast.Div, operator.floordiv),
+        (ast.Mod, operator.mod),
+        (ast.Pow, operator.pow),
+        (ast.LShift, operator.lshift),
+        (ast.RShift, operator.rshift),
+        (ast.BitAnd, operator.and_),
+        (ast.BitOr, operator.or_),
+        (ast.BitXor, operator.xor),
+    )
+)
+
+
+AST_COMPARATORS = dict(
+    (
+        (ast.Lt, operator.lt),
+        (ast.LtE, operator.le),
+        (ast.Eq, operator.eq),
+        (ast.Gt, operator.gt),
+        (ast.GtE, operator.ge),
+        (ast.NotEq, operator.ne),
+        (ast.Is, operator.is_),
+        (ast.IsNot, operator.is_not),
+        (ast.And, operator.and_),
+        (ast.Or, operator.or_),
+        (ast.Not, operator.not_),
+        (ast.In, operator.contains),
+    )
+)
+
+
 class Analyzer(ast.NodeVisitor):
     def __init__(self):
         self.name_types = []
@@ -657,10 +701,7 @@ class Analyzer(ast.NodeVisitor):
         super(Analyzer, self).generic_visit(node)
 
     def visit_BinOp(self, node):
-        left = ast_unparse(node.left, initial_mapping=True)
-        right = ast_unparse(node.right, initial_mapping=True)
-        node.left = left
-        node.right = right
+        node = ast_unparse(node, initial_mapping=True)
         self.binOps.append(node)
 
     def unmap_binops(self):
@@ -683,6 +724,11 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True):  # no
     unparse = partial(
         ast_unparse, initial_mapping=initial_mapping, analyzer=analyzer, recurse=recurse
     )
+    if getattr(ast, "Constant", None):
+        constant = (ast.Constant, ast.Ellipsis)
+    else:
+        constant = ast.Ellipsis
+    unparsed = item
     if isinstance(item, ast.Dict):
         unparsed = dict(zip(unparse(item.keys), unparse(item.values)))
     elif isinstance(item, ast.List):
@@ -693,28 +739,28 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True):  # no
         unparsed = item.s
     elif isinstance(item, ast.Subscript):
         unparsed = unparse(item.value)
+    elif any(isinstance(item, k) for k in AST_BINOP_MAP.keys()):
+        unparsed = AST_BINOP_MAP[type(item)]
+    elif isinstance(item, ast.Num):
+        unparsed = item.n
     elif isinstance(item, ast.BinOp):
         if analyzer and item in analyzer.binOps_map:
             unparsed = analyzer.binOps_map[item]
-        elif isinstance(item.op, ast.Add):
+        else:
+            right_item = unparse(item.right)
+            left_item = unparse(item.left)
+            op = getattr(item, "op", None)
+            op_func = unparse(op) if op is not None else op
             if not initial_mapping:
-                right_item = unparse(item.right)
-                left_item = unparse(item.left)
-                if not all(
-                    isinstance(side, (six.string_types, int, float, list, tuple))
-                    for side in (left_item, right_item)
-                ):
-                    item.left = left_item
-                    item.right = right_item
-                    unparsed = item
-                else:
-                    unparsed = left_item + right_item
+                try:
+                    unparsed = op_func(left_item, right_item)
+                except Exception:
+                    unparsed = (left_item, op_func, right_item)
             else:
+                item.left = left_item
+                item.right = right_item
+                item.op = op_func
                 unparsed = item
-        elif isinstance(item.op, ast.Sub):
-            unparsed = unparse(item.left) - unparse(item.right)
-        else:
-            unparsed = item
     elif isinstance(item, ast.Name):
         if not initial_mapping:
             unparsed = item.id
@@ -731,6 +777,48 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True):  # no
             unparsed = item
     elif six.PY3 and isinstance(item, ast.NameConstant):
         unparsed = item.value
+    elif any(isinstance(item, k) for k in AST_COMPARATORS.keys()):
+        unparsed = AST_COMPARATORS[type(item)]
+    elif isinstance(item, constant):
+        unparsed = item.value
+    elif isinstance(item, ast.Compare):
+        if isinstance(item.left, ast.Attribute):
+            import importlib
+
+            left = unparse(item.left)
+            if "." in left:
+                name, _, val = left.rpartition(".")
+                left = getattr(importlib.import_module(name), val, left)
+            comparators = []
+            for comparator in item.comparators:
+                right = unparse(comparator)
+                if isinstance(comparator, ast.Attribute) and "." in right:
+                    name, _, val = right.rpartition(".")
+                    right = getattr(importlib.import_module(name), val, right)
+                comparators.append(right)
+            unparsed = (left, unparse(item.ops), comparators)
+    elif isinstance(item, ast.IfExp):
+        if initial_mapping:
+            unparsed = item
+        else:
+            ops, truth_vals = [], []
+            if isinstance(item.test, ast.Compare):
+                left, ops, right = unparse(item.test)
+            else:
+                result = ast_unparse(item.test)
+                if isinstance(result, dict):
+                    k, v = result.popitem()
+                    if not v:
+                        truth_vals = [False]
+            for i, op in enumerate(ops):
+                if i == 0:
+                    truth_vals.append(op(left, right[i]))
+                else:
+                    truth_vals.append(op(right[i - 1], right[i]))
+            if all(truth_vals):
+                unparsed = unparse(item.body)
+            else:
+                unparsed = unparse(item.orelse)
     elif isinstance(item, ast.Attribute):
         attr_name = getattr(item, "value", None)
         attr_attr = getattr(item, "attr", None)
@@ -752,13 +840,21 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True):  # no
             unparsed = name if not unparsed else unparsed
     elif isinstance(item, ast.Call):
         unparsed = {}
-        if isinstance(item.func, ast.Name):
-            func_name = unparse(item.func)
-        elif isinstance(item.func, ast.Attribute):
+        if isinstance(item.func, (ast.Name, ast.Attribute)):
             func_name = unparse(item.func)
-        if func_name:
+        else:
+            try:
+                func_name = unparse(item.func)
+            except Exception:
+                func_name = None
+        if isinstance(func_name, dict):
+            unparsed.update(func_name)
+            func_name = next(iter(func_name.keys()))
+            for keyword in getattr(item, "keywords", []):
+                unparsed[func_name].update(unparse(keyword))
+        elif func_name:
             unparsed[func_name] = {}
-            for keyword in item.keywords:
+            for keyword in getattr(item, "keywords", []):
                 unparsed[func_name].update(unparse(keyword))
     elif isinstance(item, ast.keyword):
         unparsed = {unparse(item.arg): unparse(item.value)}
@@ -787,8 +883,6 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True):  # no
         unparsed = type(item)([unparse(el) for el in item])
     elif isinstance(item, six.string_types):
         unparsed = item
-    else:
-        return item
     return unparsed
 
 
diff --git a/src/requirementslib/models/utils.py b/src/requirementslib/models/utils.py
index 5d47086..9b6beb6 100644
--- a/src/requirementslib/models/utils.py
+++ b/src/requirementslib/models/utils.py
@@ -12,7 +12,6 @@ from itertools import chain, groupby
 import six
 import tomlkit
 from attr import validators
-from first import first
 from packaging.markers import InvalidMarker, Marker, Op, Value, Variable
 from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
 from packaging.version import parse as parse_version
@@ -103,6 +102,11 @@ def filter_none(k, v):
     return False
 
 
+def filter_dict(dict_):
+    # type: (Dict[AnyStr, Any]) -> Dict[AnyStr, Any]
+    return {k: v for k, v in dict_.items() if filter_none(k, v)}
+
+
 def optional_instance_of(cls):
     # type: (Any) -> _ValidatorType[Optional[_T]]
     return validators.optional(validators.instance_of(cls))
@@ -548,8 +552,9 @@ def split_vcs_method_from_uri(uri):
     # type: (AnyStr) -> Tuple[Optional[STRING_TYPE], STRING_TYPE]
     """Split a vcs+uri formatted uri into (vcs, uri)"""
     vcs_start = "{0}+"
-    vcs = None  # type: Optional[STRING_TYPE]
-    vcs = first([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))])
+    vcs = next(
+        iter([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))]), None
+    )
     if vcs:
         vcs, uri = uri.split("+", 1)
     return vcs, uri
@@ -766,7 +771,7 @@ def as_tuple(ireq):
         raise TypeError("Expected a pinned InstallRequirement, got {}".format(ireq))
 
     name = key_from_req(ireq.req)
-    version = first(ireq.specifier._specs)._spec[1]
+    version = next(iter(ireq.specifier._specs))._spec[1]
     extras = tuple(sorted(ireq.extras))
     return name, version, extras
 
@@ -906,7 +911,7 @@ def version_from_ireq(ireq):
     :rtype: str
     """
 
-    return first(ireq.specifier._specs).version
+    return next(iter(ireq.specifier._specs)).version
 
 
 def _get_requires_python(candidate):


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index dadc614..0ad56c1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,9 +10,6 @@ import pytest
 import requests
 import vistir
 
-import requirementslib.utils
-from requirementslib.models.setup_info import SetupInfo
-
 CURRENT_FILE = vistir.compat.Path(__file__).absolute()
 
 
@@ -107,6 +104,8 @@ def pip_src_dir(request, pathlib_tmpdir):
 
 @pytest.fixture(autouse=True)
 def monkeypatch_if_needed(monkeypatch):
+    from requirementslib.models.setup_info import SetupInfo
+
     with monkeypatch.context() as m:
         if SKIP_INTERNET:
             m.setattr(pip_shims.shims, "unpack_url", mock_unpack)
@@ -116,6 +115,8 @@ def monkeypatch_if_needed(monkeypatch):
 
 @pytest.fixture(scope="session")
 def artifact_dir():
+    import requirementslib.utils
+
     return (
         vistir.compat.Path(requirementslib.utils.__file__)
         .absolute()
@@ -145,6 +146,8 @@ def pipfile_dir(fixture_dir):
 
 @pytest.fixture
 def test_artifact(artifact_dir, pathlib_tmpdir, request):
+    import requirementslib.utils
+
     name = request.param["name"]
     as_artifact = request.param.get("as_artifact", False)
     target = artifact_dir.joinpath(name)
diff --git a/tests/fixtures/setup_py/package_with_conditional_install_requires/README.rst b/tests/fixtures/setup_py/package_with_conditional_install_requires/README.rst
new file mode 100644
index 0000000..e652f90
--- /dev/null
+++ b/tests/fixtures/setup_py/package_with_conditional_install_requires/README.rst
@@ -0,0 +1,1 @@
+This file is fake
diff --git a/tests/fixtures/setup_py/package_with_conditional_install_requires/setup.py b/tests/fixtures/setup_py/package_with_conditional_install_requires/setup.py
new file mode 100644
index 0000000..77a26da
--- /dev/null
+++ b/tests/fixtures/setup_py/package_with_conditional_install_requires/setup.py
@@ -0,0 +1,61 @@
+import sys
+
+from setuptools import setup
+
+try:
+    from non_existant_pkg import cmdclass
+except ImportError:
+    from distutils import log as logger
+
+    logger.warn("Wheel is not available, disabling bdist_wheel hook")
+    cmdclass = {}
+
+try:
+    import incompatible_pkg
+
+    try:
+        ver = incompatible_pkg.__version__
+        raise Exception(
+            "This package is incompatible with incompatible_pkg=={}. ".format(ver)
+            + 'Uninstall it with "pip uninstall azure".'
+        )
+    except AttributeError:
+        pass
+except ImportError:
+    pass
+
+setup(
+    name="fakepkg",
+    version="0.1.0",
+    description="Fake package",
+    long_description=open("README.rst", "r").read(),
+    license="MIT",
+    author="Fake Author",
+    author_email="Fake@fake.com",
+    url="https://github.com/fake/fakepackage",
+    classifiers=[
+        "Development Status :: 4 - Beta",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.3",
+        "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "License :: OSI Approved :: MIT License",
+    ],
+    zip_safe=True,
+    package_dir={"": "src"},
+    packages=["fakepkg"],
+    install_requires=[
+        "azure-common>=1.1.5",
+        "cryptography",
+        "python-dateutil",
+        "requests",
+    ]
+    + (["futures"] if sys.version_info < (3, 0) else []),
+    cmdclass=cmdclass,
+)
diff --git a/tests/fixtures/setup_py/package_with_conditional_install_requires/src/fakepkg/__init__.py b/tests/fixtures/setup_py/package_with_conditional_install_requires/src/fakepkg/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/unit/test_markers.py b/tests/unit/test_markers.py
index 701eabf..5121ab6 100644
--- a/tests/unit/test_markers.py
+++ b/tests/unit/test_markers.py
@@ -136,7 +136,7 @@ def test_get_extras(marker, extras):
             Marker(
                 "os_name == 'posix' and python_version >= '2.7' and python_version not in '3.0.*,3.1.*,3.2.*,3.3.*'"
             ),
-            SpecifierSet("!=3.0,!=3.1,!=3.2,!=3.3,>=2.7"),
+            SpecifierSet("!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"),
         ),
     ],
 )
diff --git a/tests/unit/test_requirements.py b/tests/unit/test_requirements.py
index ec178d7..04338f9 100644
--- a/tests/unit/test_requirements.py
+++ b/tests/unit/test_requirements.py
@@ -3,7 +3,6 @@ import os
 
 import pip_shims.shims
 import pytest
-from first import first
 from hypothesis import given, settings, strategies as st
 from vistir.compat import Path
 
@@ -212,7 +211,7 @@ def test_convert_from_pip(monkeypatch, expected, requirement):
         m.setattr(SetupInfo, "get_info", mock_run_requires)
         m.setattr(Line, "get_setup_info", mock_run_requires)
         m.setattr(pip_shims.shims, "unpack_url", mock_unpack)
-        pkg_name = first(expected.keys())
+        pkg_name = next(iter(expected.keys()))
         pkg_pipfile = expected[pkg_name]
         line = Line(requirement)
         if (
@@ -236,7 +235,7 @@ def test_convert_from_pipfile(monkeypatch, requirement, expected):
         m.setattr(pip_shims.shims, "unpack_url", mock_unpack)
         m.setattr(SetupInfo, "get_info", mock_run_requires)
         m.setattr(Requirement, "run_requires", mock_run_requires)
-        pkg_name = first(requirement.keys())
+        pkg_name = next(iter(requirement.keys()))
         pkg_pipfile = requirement[pkg_name]
         req = Requirement.from_pipfile(pkg_name, pkg_pipfile)
         if " (" in expected and expected.endswith(")"):
@@ -283,7 +282,7 @@ def test_convert_non_installable_dir_fail(pathlib_tmpdir):
 def test_one_way_editable_extras():
     dep = "-e .[socks]"
     dep = Requirement.from_line(dep).as_pipfile()
-    k = first(dep.keys())
+    k = next(iter(dep.keys()))
     assert dep[k]["extras"] == ["socks"]
 
 
@@ -438,10 +437,7 @@ def test_pep_508():
         "tablib@ https://codeload.github.com/kennethreitz/tablib/zip/v0.12.1"
     )
     assert r.specifiers == "==0.12.1"
-    assert (
-        r.req.link.url
-        == "https://codeload.github.com/kennethreitz/tablib/zip/v0.12.1"
-    )
+    assert r.req.link.url == "https://codeload.github.com/kennethreitz/tablib/zip/v0.12.1"
     assert r.req.req.name == "tablib"
     assert r.req.req.url == "https://codeload.github.com/kennethreitz/tablib/zip/v0.12.1"
     requires, setup_requires, build_requires = r.req.dependencies
diff --git a/tests/unit/test_setup_info.py b/tests/unit/test_setup_info.py
index 1878d77..565cb2a 100644
--- a/tests/unit/test_setup_info.py
+++ b/tests/unit/test_setup_info.py
@@ -211,6 +211,23 @@ def test_ast_parser_finds_fully_qualified_setup(setup_py_dir):
             assert parsed[k] == v, parsed[k]
 
 
+def test_ast_parser_handles_binops(setup_py_dir):
+    parsed = ast_parse_setup_py(
+        setup_py_dir.joinpath(
+            "package_with_conditional_install_requires/setup.py"
+        ).as_posix()
+    )
+    expected = [
+        "azure-common>=1.1.5",
+        "cryptography",
+        "python-dateutil",
+        "requests",
+    ]
+    if six.PY2:
+        expected.append("futures")
+    assert list(sorted(parsed["install_requires"])) == list(sorted(expected))
+
+
 def test_setup_cfg_parser(setup_cfg_dir):
     setup_path = setup_cfg_dir / "package_with_multiple_extras/setup.cfg"
     if six.PY2: