{"action":"created","comment":{"url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/comments/203444341","pull_request_review_id":138334926,"id":203444341,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwMzQ0NDM0MQ==","diff_hunk":"@@ -82,314 +245,401 @@ test('inspect', function (t) {\n         },\n       }, 'django-select2 looks ok');\n \n-      t.match(pkg.dependencies['irc'], {\n-        name: 'irc',\n-        version: '16.2',\n-        dependencies: {\n-          'more-itertools': {},\n-          'jaraco.functools': {},\n-          'jaraco.collections': {\n-            dependencies: {\n-              'jaraco.text': {},\n-            }\n-          },\n-          'jaraco.text': {\n-            dependencies: {\n-              'jaraco.collections': {}\n-            }\n-          },\n-        }\n-      }, 'irc ok, even though it has a cyclic dep, yay!')\n-\n-      t.match(pkg.dependencies['testtools'], {\n-        name: 'testtools',\n-        version: '2.3.0',\n-        dependencies: {\n-          'pbr': {},\n-          'extras': {},\n-          'fixtures': {},\n-          'unittest2': {},\n-          'traceback2': {},\n-          'python-mimeparse': {},\n-        }\n-      }, 'testtools ok, even though it\\'s cyclic, yay!')\n-\n       t.end();\n     });\n \n     t.end();\n   });\n });\n \n-test('transitive dep not installed', function (t) {\n-  chdirWorkspaces('pip-app');\n-  return pipUninstall('MarkupSafe')\n+test('deps not installed', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-deps-not-installed');\n+    t.teardown(testUtils.activateVirtualenv('pip-app'));\n+  })\n   .then(function () {\n-      return plugin.inspect('.', 'requirements.txt')\n-        .then(function () {\n-          t.fail('should have failed');\n-        })\n-        .catch(function (error) {\n-          t.equal(error.message, 'Please run `pip install -r requirements.txt`');\n-        });\n-    })\n+    return plugin.inspect('.', 'requirements.txt')\n+  })\n+  .then(function () {\n+    t.fail('should have failed');\n+  })\n+  .catch(function (error) {\n+    t.equal(error.message, 'Please run `pip install -r requirements.txt`');\n+    t.end();\n+  });\n });\n \n-test('transitive dep not installed, but with allowMissing option', function (t) {\n-  chdirWorkspaces('pip-app');\n-\n-  return pipInstall()\n-    .then(function () {\n-      return pipUninstall('MarkupSafe')\n-    }).then(function () {\n-      return plugin.inspect('.', 'requirements.txt', {allowMissing: true})\n-        .then(function (result) {\n-          var plugin = result.plugin;\n-          var pkg = result.package;\n-\n-          t.test('plugin', function (t) {\n-            t.ok(plugin, 'plugin');\n-            t.equal(plugin.name, 'snyk-python-plugin', 'name');\n-            t.match(plugin.runtime, 'Python', 'runtime');\n-            t.end();\n-          });\n-\n-          t.test('package', function (t) {\n-            t.ok(pkg, 'package');\n-            t.equal(pkg.name, 'pip-app', 'name');\n-            t.equal(pkg.version, '0.0.0', 'version');\n-            // t.equal(pkg.full, 'pip-app@0.0.0', 'full'); // do we need this?\n-            t.end();\n-          });\n-\n-          t.test('package dependencies', function (t) {\n-            t.same(pkg.dependencies.django, {\n-              name: 'django',\n-              version: '1.6.1',\n-            }, 'django looks ok');\n-\n-            t.match(pkg.dependencies.jinja2, {\n-              name: 'jinja2',\n-              version: '2.7.2',\n-              dependencies: {},\n-            }, 'jinja2 looks ok');\n-\n-            t.match(pkg.dependencies['python-etcd'], {\n-              name: 'python-etcd',\n-              version: '0.4.5',\n-              dependencies: {\n-                dnspython: {\n-                  name: 'dnspython',\n-                  version: /.+$/,\n-                },\n-                urllib3: {\n-                  name: 'urllib3',\n-                  version: /.+$/,\n-                },\n-              },\n-            }, 'python-etcd is ok');\n-\n-            t.match(pkg.dependencies['django-select2'], {\n-              name: 'django-select2',\n-              version: '6.0.1',\n-              dependencies: {\n-                'django-appconf': {\n-                  name: 'django-appconf',\n-                },\n-              },\n-            }, 'django-select2 looks ok');\n-\n-            t.end();\n-          });\n-\n-          t.end();\n-        });\n-\n-    })\n-});\n+test('deps not installed, but with allowMissing option', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-deps-not-installed');\n+    t.teardown(testUtils.activateVirtualenv('pip-app'));\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'requirements.txt', { allowMissing: true })\n+  })\n+  .then(function (result) {\n+    var plugin = result.plugin;\n+    var pkg = result.package;\n \n-test('deps not installed', function (t) {\n-  chdirWorkspaces('pip-app-deps-not-installed');\n-  return plugin.inspect('.', 'requirements.txt')\n-    .then(function () {\n-      t.fail('should have failed');\n-    })\n-    .catch(function (error) {\n-      t.equal(error.message, 'Please run `pip install -r requirements.txt`');\n+    t.test('plugin', function (t) {\n+      t.ok(plugin, 'plugin');\n+      t.equal(plugin.name, 'snyk-python-plugin', 'name');\n+      t.match(plugin.runtime, 'Python', 'runtime');\n+      t.end();\n     });\n-});\n \n-test('deps not installed, but with allowMissing option', function (t) {\n-  chdirWorkspaces('pip-app-deps-not-installed');\n-  return plugin.inspect('.', 'requirements.txt', { allowMissing: true })\n-    .then(function (result) {\n-      var plugin = result.plugin;\n-      var pkg = result.package;\n-\n-      t.test('plugin', function (t) {\n-        t.ok(plugin, 'plugin');\n-        t.equal(plugin.name, 'snyk-python-plugin', 'name');\n-        t.match(plugin.runtime, 'Python', 'runtime');\n-        t.end();\n-      });\n-\n-      t.test('package', function (t) {\n-        t.ok(pkg, 'package');\n-        t.equal(pkg.name, 'pip-app-deps-not-installed', 'name');\n-        t.equal(pkg.version, '0.0.0', 'version');\n-        t.end();\n-      });\n+    t.test('package', function (t) {\n+      t.ok(pkg, 'package');\n+      t.equal(pkg.name, 'pip-app-deps-not-installed', 'name');\n+      t.equal(pkg.version, '0.0.0', 'version');\n+      t.end();\n     });\n+\n+    t.end();\n+  });\n });\n \n test('uses provided exec command', function (t) {\n-  var command = 'echo';\n-  var execute = sinon.stub(subProcess, 'execute');\n-  execute.onFirstCall().returns(Promise.resolve('abc'));\n-  execute.onSecondCall().returns(Promise.resolve('{}'));\n-  t.teardown(execute.restore);\n-\n-  return plugin.inspect('.', 'requirements.txt', {\n-    command: command,\n+  return Promise.resolve().then(function () {\n+    var execute = sinon.stub(subProcess, 'execute');\n+    execute.onFirstCall().returns(Promise.resolve('abc'));\n+    execute.onSecondCall().returns(Promise.resolve('{}'));\n+    t.teardown(execute.restore);\n+    return execute;\n   })\n-    .then(function () {\n+  .then(function (execute) {\n+    var command = 'echo';\n+    return plugin.inspect('.', 'requirements.txt', { command: command })\n+    .then(function (result) {\n       t.ok(execute.calledTwice, 'execute called twice');\n       t.equal(execute.firstCall.args[0], command, 'uses command');\n       t.equal(execute.secondCall.args[0], command, 'uses command');\n+      t.end();\n     });\n+  })\n });\n \n test('package name differs from requirement', function (t) {\n-  chdirWorkspaces('pip-app-deps-with-dashes');\n-  return pipInstall()\n-    .then(function () {\n-      return plugin.inspect('.', 'requirements.txt')\n-        .then(function (result) {\n-          var pkg = result.package;\n-          t.same(pkg.dependencies['dj-database-url'], {\n-            name: 'dj-database-url',\n-            version: '0.4.2',\n-          }, 'dj-database-url looks ok');\n-          t.same(pkg.dependencies['posix-ipc'], {\n-            name: 'posix-ipc',\n-            version: '1.0.0',\n-          }, 'posix-ipc looks ok');\n-          t.end();\n-        });\n-    })\n-    .catch(function (error) {\n-      t.fail(error);\n-    });\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-deps-with-dashes');\n+    var venvCreated = testUtils.ensureVirtualenv('pip-app-deps-with-dashes');\n+    t.teardown(testUtils.activateVirtualenv('pip-app-deps-with-dashes'));\n+    if (venvCreated) {\n+      testUtils.pipInstall();\n+    }\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'requirements.txt')\n+  })\n+  .then(function (result) {\n+    var pkg = result.package;\n+    t.same(pkg.dependencies['dj-database-url'], {\n+      name: 'dj-database-url',\n+      version: '0.4.2',\n+    }, 'dj-database-url looks ok');\n+    t.same(pkg.dependencies['posix-ipc'], {\n+      name: 'posix-ipc',\n+      version: '1.0.0',\n+    }, 'posix-ipc looks ok');\n+    t.end();\n+  });\n });\n \n test('package depends on platform', function (t) {\n-  chdirWorkspaces('pip-app-deps-conditional');\n-  return pipInstall()\n-    .then(function () {\n-      return plugin.inspect('.', 'requirements.txt')\n-        .then(function (result) {\n-          var pkg = result.package;\n-          t.notOk(pkg.dependencies.pypiwin32, 'win32 dep ignored');\n-          t.same(pkg.dependencies['posix-ipc'], {\n-            name: 'posix-ipc',\n-            version: '1.0.0',\n-          }, 'posix-ipc looks ok');\n-          t.end();\n-        });\n-    })\n-    .catch(function (error) {\n-      t.fail(error);\n-    });\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-deps-conditional');\n+    var venvCreated = testUtils.ensureVirtualenv('pip-app-deps-conditional');\n+    t.teardown(testUtils.activateVirtualenv('pip-app-deps-conditional'));\n+    if (venvCreated) {\n+      testUtils.pipInstall();\n+    }\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'requirements.txt')\n+  })\n+  .then(function (result) {\n+    var pkg = result.package;\n+    t.notOk(pkg.dependencies.pypiwin32, 'win32 dep ignored');\n+    t.same(pkg.dependencies['posix-ipc'], {\n+      name: 'posix-ipc',\n+      version: '1.0.0',\n+    }, 'posix-ipc looks ok');\n+    t.end();\n+  });\n });\n \n test('editables ignored', function (t) {\n-  chdirWorkspaces('pip-app-deps-editable');\n-  return pipInstall()\n-    .then(function () {\n-      return plugin.inspect('.', 'requirements.txt')\n-        .then(function (result) {\n-          var pkg = result.package;\n-          t.notOk(pkg.dependencies['simple'], 'editable dep ignored');\n-          t.notOk(pkg.dependencies['sample'], 'editable subdir dep ignored');\n-          t.same(pkg.dependencies['posix-ipc'], {\n-            name: 'posix-ipc',\n-            version: '1.0.0',\n-          }, 'posix-ipc looks ok');\n-          t.end();\n-        });\n-    })\n-    .catch(function (error) {\n-      t.fail(error);\n-    });\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-deps-editable');\n+    var venvCreated = testUtils.ensureVirtualenv('pip-app-deps-editable');\n+    t.teardown(testUtils.activateVirtualenv('pip-app-deps-editable'));\n+    if (venvCreated) {\n+      testUtils.pipInstall();\n+    }\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'requirements.txt')\n+  })\n+  .then(function (result) {\n+    var pkg = result.package;\n+    t.notOk(pkg.dependencies['simple'], 'editable dep ignored');\n+    t.notOk(pkg.dependencies['sample'], 'editable subdir dep ignored');\n+    t.same(pkg.dependencies['posix-ipc'], {\n+      name: 'posix-ipc',\n+      version: '1.0.0',\n+    }, 'posix-ipc looks ok');\n+    t.end();\n+  });\n });\n \n-test('deps withs options', function (t) {\n-  chdirWorkspaces('pip-app-with-options');\n+test('deps with options', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-with-options');\n+    var venvCreated = testUtils.ensureVirtualenv('pip-app-with-options');\n+    t.teardown(testUtils.activateVirtualenv('pip-app-with-options'));\n+    if (venvCreated) {\n+      testUtils.pipInstall();\n+    }\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'requirements.txt')\n+  })\n+  .then(function (result) {\n+    var plugin = result.plugin;\n+    var pkg = result.package;\n \n-  return pipInstall()\n-    .then(function () {\n-      return plugin.inspect('.', 'requirements.txt')\n-        .then(function (result) {\n-          var plugin = result.plugin;\n-          var pkg = result.package;\n-\n-          t.test('plugin', function (t) {\n-            t.ok(plugin, 'plugin');\n-            t.equal(plugin.name, 'snyk-python-plugin', 'name');\n-            t.match(plugin.runtime, 'Python', 'runtime');\n-            t.end();\n-          });\n-\n-          t.test('package', function (t) {\n-            t.ok(pkg, 'package');\n-            t.equal(pkg.name, 'pip-app-with-options', 'name');\n-            t.equal(pkg.version, '0.0.0', 'version');\n-            t.end();\n-          });\n-\n-          t.test('package dependencies', function (t) {\n-            t.match(pkg.dependencies.markupsafe, {\n-              name: 'markupsafe',\n-              version: '1.0',\n-            }, 'MarkupSafe looks ok');\n-\n-            t.match(pkg.dependencies.dnspython, {\n-              name: 'dnspython',\n-              version: '1.13.0',\n-            }, 'dnspython looks ok');\n-\n-            t.end();\n-          });\n-\n-          t.end();\n-        });\n+    t.test('plugin', function (t) {\n+      t.ok(plugin, 'plugin');\n+      t.equal(plugin.name, 'snyk-python-plugin', 'name');\n+      t.match(plugin.runtime, 'Python', 'runtime');\n+      t.end();\n     });\n+\n+    t.test('package', function (t) {\n+      t.ok(pkg, 'package');\n+      t.equal(pkg.name, 'pip-app-with-options', 'name');\n+      t.equal(pkg.version, '0.0.0', 'version');\n+      t.end();\n+    });\n+\n+    t.test('package dependencies', function (t) {\n+      t.match(pkg.dependencies.markupsafe, {\n+        name: 'markupsafe',\n+        version: '1.0',\n+      }, 'MarkupSafe looks ok');\n+\n+      t.match(pkg.dependencies.dnspython, {\n+        name: 'dnspython',\n+        version: '1.13.0',\n+      }, 'dnspython looks ok');\n+\n+      t.end();\n+    });\n+\n+    t.end();\n+  });\n });\n \n test('trusted host ignored', function (t) {\n-  chdirWorkspaces('pip-app-trusted-host');\n-  return pipInstall()\n-    .then(function () {\n-      return plugin.inspect('.', 'requirements.txt')\n-        .then(function (result) {\n-          t.ok(result.package.dependencies, 'does not error');\n-          t.end();\n-        });\n-    })\n-    .catch(function (error) {\n-      t.fail(error);\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pip-app-trusted-host');\n+    var venvCreated = testUtils.ensureVirtualenv('pip-app-trusted-host');\n+    t.teardown(testUtils.activateVirtualenv('pip-app-trusted-host'));\n+    if (venvCreated) {\n+      testUtils.pipInstall();\n+    }\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'requirements.txt')\n+  })\n+  .then(function (result) {\n+    t.ok(result.package.dependencies, 'does not error');\n+    t.end();\n+  });\n+});\n+\n+test('inspect Pipfile', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pipfile-pipapp');\n+    t.teardown(testUtils.activateVirtualenv('pip-app'));\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'Pipfile')\n+  })\n+  .then(function (result) {\n+    var pkg = result.package;\n+\n+    t.test('package dependencies', function (t) {\n+      t.notOk(pkg.dependencies['django'], 'django skipped (editable)');\n+\n+      t.match(pkg.dependencies['django-select2'], {\n+        name: 'django-select2',\n+        version: '6.0.1',\n+        dependencies: {\n+          'django-appconf': {\n+            name: 'django-appconf',\n+          },\n+        },\n+      }, 'django-select2 looks ok');\n+\n+      t.match(pkg.dependencies['python-etcd'], {\n+        name: 'python-etcd',\n+        version: /^0\\.4.*$/,\n+      }, 'python-etcd looks ok');\n+\n+      t.notOk(pkg.dependencies['e1839a8'],\n+        'dummy local package skipped (editable)');\n+\n+      t.ok(pkg.dependencies['jinja2'] !== undefined, 'jinja2 found');\n+      t.ok(pkg.dependencies['testtools'] !== undefined, 'testtools found');\n+\n+      t.end();\n     });\n+\n+    t.end();\n+  });\n });\n \n-function pipInstall() {\n-  return subProcess.execute('pip',\n-    ['install', '-r', 'requirements.txt', '--disable-pip-version-check']);\n-}\n+test('inspect Pipfile with pinned versions', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pipfile-pipapp-pinned');\n+    t.teardown(testUtils.activateVirtualenv('pip-app'));\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'Pipfile')\n+  })\n+  .then(function (result) {\n+    var pkg = result.package;\n+\n+    t.test('package dependencies', function (t) {\n+      Object.keys(pipAppExpectedDependencies).forEach(function (depName) {\n+        t.match(\n+          pkg.dependencies[depName],\n+          pipAppExpectedDependencies[depName].data,\n+          pipAppExpectedDependencies[depName].msg\n+        );\n+      });\n+\n+      t.end();\n+    });\n+\n+    t.end();\n+  });\n+});\n+\n+var pipenvAppExpectedDependencies = {\n+  'python-etcd': {\n+    data: {\n+      name: 'python-etcd',\n+      version: /^0\\.4/,\n+    },\n+    msg: 'python-etcd1 found with version >=0.4,<0.5',\n+  },\n+  jinja2: {\n+    data: {\n+      name: 'jinja2',\n+      version: /^0|1|2\\.[0-6]/,\n+    },\n+    msg: 'jinja2 found with version <2.7',\n+  },\n+  testtools: {\n+    data: {\n+      name: 'testtools',\n+    },\n+    msg: 'testtools found',\n+  },\n+};\n+\n+test('inspect pipenv app with user-created virtualenv', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pipenv-app');\n+\n+    var venvCreated = testUtils.ensureVirtualenv('pipenv-app');\n+    t.teardown(testUtils.activateVirtualenv('pipenv-app'));\n+    if (venvCreated) {\n+      return testUtils.pipenvInstall();\n+    }\n+  })\n+  .then(function () {\n+    return plugin.inspect('.', 'Pipfile')\n+  })\n+  .then(function (result) {\n+    var pkg = result.package;\n+\n+    t.test('package dependencies', function (t) {\n+      Object.keys(pipenvAppExpectedDependencies).forEach(function (depName) {\n+        t.match(\n+          pkg.dependencies[depName],\n+          pipenvAppExpectedDependencies[depName].data,\n+          pipenvAppExpectedDependencies[depName].msg\n+        );\n+      });\n+\n+      t.end();\n+    });\n+\n+    t.end();\n+  });\n+});\n+\n+test('inspect pipenv app with auto-created virtualenv', function (t) {\n+  return Promise.resolve().then(function () {\n+    chdirWorkspaces('pipenv-app');\n+\n+    // Use several teardown callbacks, called in reverse order.\n+    var teardowns = [];\n+    t.teardown(function () {\n+      while (teardowns.length > 0) {\n+        teardowns.pop()();\n+      }\n+    });\n+\n+    // Deactivate current virtualenv if active.\n+    //\n+    // Note: The pipenv execuable within the virtualenv will still be used to\n+    // run pipenv despite the virtualenv being deactivated.\n+    if (testUtils.getActiveVenvName() !== null) {\n+      teardowns.push(testUtils.deactivateVirtualenv());\n+    }\n+\n+    // Set the WORKON_HOME env var to make pipenv put its auto-created\n+    // virtualenv where we want it.\n+    teardowns.push(testUtils.setWorkonHome());\n+\n+    // Have pipenv create and update a virtualenv if it doesn't exist.\n+    var proc = subProcess.executeSync('pipenv', ['--venv']);\n+    if (proc.status !== 0) {\n+      teardowns.push(function () {\n+        fs.unlinkSync('Pipfile.lock', function (err) {});\n+      });\n+      var updateProc = subProcess.executeSync('pipenv', ['update']);","path":"test/inspect.test.js","position":860,"original_position":860,"commit_id":"d62f7e636d2cb88767b03926e513bf93e44afb3b","original_commit_id":"d62f7e636d2cb88767b03926e513bf93e44afb3b","user":{"login":"michael-go","id":4750004,"node_id":"MDQ6VXNlcjQ3NTAwMDQ=","avatar_url":"https://avatars3.githubusercontent.com/u/4750004?v=4","gravatar_id":"","url":"https://api.github.com/users/michael-go","html_url":"https://github.com/michael-go","followers_url":"https://api.github.com/users/michael-go/followers","following_url":"https://api.github.com/users/michael-go/following{/other_user}","gists_url":"https://api.github.com/users/michael-go/gists{/gist_id}","starred_url":"https://api.github.com/users/michael-go/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/michael-go/subscriptions","organizations_url":"https://api.github.com/users/michael-go/orgs","repos_url":"https://api.github.com/users/michael-go/repos","events_url":"https://api.github.com/users/michael-go/events{/privacy}","received_events_url":"https://api.github.com/users/michael-go/received_events","type":"User","site_admin":false},"body":"why `update` and not `install`?","created_at":"2018-07-18T16:23:42Z","updated_at":"2018-07-18T16:23:42Z","html_url":"https://github.com/snyk/snyk-python-plugin/pull/38#discussion_r203444341","pull_request_url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/comments/203444341"},"html":{"href":"https://github.com/snyk/snyk-python-plugin/pull/38#discussion_r203444341"},"pull_request":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38"}}},"pull_request":{"url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38","id":195929002,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk1OTI5MDAy","html_url":"https://github.com/snyk/snyk-python-plugin/pull/38","diff_url":"https://github.com/snyk/snyk-python-plugin/pull/38.diff","patch_url":"https://github.com/snyk/snyk-python-plugin/pull/38.patch","issue_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/38","number":38,"state":"open","locked":false,"title":"feat: Pipfile (pipenv) initial support","user":{"login":"taleinat","id":532281,"node_id":"MDQ6VXNlcjUzMjI4MQ==","avatar_url":"https://avatars3.githubusercontent.com/u/532281?v=4","gravatar_id":"","url":"https://api.github.com/users/taleinat","html_url":"https://github.com/taleinat","followers_url":"https://api.github.com/users/taleinat/followers","following_url":"https://api.github.com/users/taleinat/following{/other_user}","gists_url":"https://api.github.com/users/taleinat/gists{/gist_id}","starred_url":"https://api.github.com/users/taleinat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/taleinat/subscriptions","organizations_url":"https://api.github.com/users/taleinat/orgs","repos_url":"https://api.github.com/users/taleinat/repos","events_url":"https://api.github.com/users/taleinat/events{/privacy}","received_events_url":"https://api.github.com/users/taleinat/received_events","type":"User","site_admin":false},"body":"- [x] Ready for review\r\n- [x] Follows CONTRIBUTING rules\r\n- [ ] Reviewed by Snyk internal team\r\n\r\n#### What does this PR do?\r\nAdds initial, basic support for Python projects using `Pipfile` files for dependency management, specifically e.g. via `pipenv`.\r\n\r\n#### Where should the reviewer start?\r\nThe changes in `pip_resolve.py`, the new `pipfile.py` module, and `inspect.test.js`.\r\n\r\n#### How should this be manually tested?\r\n1. `npm test`\r\n2. using `manual.js`\r\n\r\n#### Any background context you want to provide?\r\n* The Pipfile parser I wrote is very basic and only extracts the info this plugin currently needs. This was the minimum needed to avoid depending on `pipenv`; it did require vendoring the `pytoml` library, though.\r\n* I included a couple of \"TODO\" comments where ISTM that the code should be refactored, but I wanted to get some feedback first before making more extensive changes.\r\n* The tests are now run in various separate virtualenvs. They are created as needed, assumed to be set up properly if they exist, and are reused between tests when appropriate.\r\n\r\n#### What are the relevant tickets?\r\n[SC-4814](https://snyksec.atlassian.net/browse/SC-4814)\r\n\r\n#### Screenshots\r\n\r\n\r\n#### Additional questions\r\n","created_at":"2018-06-19T19:11:02Z","updated_at":"2018-07-18T16:23:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6c9122bace943c819671371ffa5e442aae518720","assignee":null,"assignees":[],"requested_reviewers":[{"login":"orkamara","id":37250770,"node_id":"MDQ6VXNlcjM3MjUwNzcw","avatar_url":"https://avatars3.githubusercontent.com/u/37250770?v=4","gravatar_id":"","url":"https://api.github.com/users/orkamara","html_url":"https://github.com/orkamara","followers_url":"https://api.github.com/users/orkamara/followers","following_url":"https://api.github.com/users/orkamara/following{/other_user}","gists_url":"https://api.github.com/users/orkamara/gists{/gist_id}","starred_url":"https://api.github.com/users/orkamara/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/orkamara/subscriptions","organizations_url":"https://api.github.com/users/orkamara/orgs","repos_url":"https://api.github.com/users/orkamara/repos","events_url":"https://api.github.com/users/orkamara/events{/privacy}","received_events_url":"https://api.github.com/users/orkamara/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38/commits","review_comments_url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38/comments","review_comment_url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/comments{/number}","comments_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/38/comments","statuses_url":"https://api.github.com/repos/snyk/snyk-python-plugin/statuses/d62f7e636d2cb88767b03926e513bf93e44afb3b","head":{"label":"snyk:pipfile_lock_initial_support","ref":"pipfile_lock_initial_support","sha":"d62f7e636d2cb88767b03926e513bf93e44afb3b","user":{"login":"snyk","id":12959162,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTU5MTYy","avatar_url":"https://avatars1.githubusercontent.com/u/12959162?v=4","gravatar_id":"","url":"https://api.github.com/users/snyk","html_url":"https://github.com/snyk","followers_url":"https://api.github.com/users/snyk/followers","following_url":"https://api.github.com/users/snyk/following{/other_user}","gists_url":"https://api.github.com/users/snyk/gists{/gist_id}","starred_url":"https://api.github.com/users/snyk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snyk/subscriptions","organizations_url":"https://api.github.com/users/snyk/orgs","repos_url":"https://api.github.com/users/snyk/repos","events_url":"https://api.github.com/users/snyk/events{/privacy}","received_events_url":"https://api.github.com/users/snyk/received_events","type":"Organization","site_admin":false},"repo":{"id":92673451,"node_id":"MDEwOlJlcG9zaXRvcnk5MjY3MzQ1MQ==","name":"snyk-python-plugin","full_name":"snyk/snyk-python-plugin","owner":{"login":"snyk","id":12959162,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTU5MTYy","avatar_url":"https://avatars1.githubusercontent.com/u/12959162?v=4","gravatar_id":"","url":"https://api.github.com/users/snyk","html_url":"https://github.com/snyk","followers_url":"https://api.github.com/users/snyk/followers","following_url":"https://api.github.com/users/snyk/following{/other_user}","gists_url":"https://api.github.com/users/snyk/gists{/gist_id}","starred_url":"https://api.github.com/users/snyk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snyk/subscriptions","organizations_url":"https://api.github.com/users/snyk/orgs","repos_url":"https://api.github.com/users/snyk/repos","events_url":"https://api.github.com/users/snyk/events{/privacy}","received_events_url":"https://api.github.com/users/snyk/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/snyk/snyk-python-plugin","description":"Basic Snyk CLI plugin for Python support","fork":false,"url":"https://api.github.com/repos/snyk/snyk-python-plugin","forks_url":"https://api.github.com/repos/snyk/snyk-python-plugin/forks","keys_url":"https://api.github.com/repos/snyk/snyk-python-plugin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/snyk/snyk-python-plugin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/snyk/snyk-python-plugin/teams","hooks_url":"https://api.github.com/repos/snyk/snyk-python-plugin/hooks","issue_events_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/events{/number}","events_url":"https://api.github.com/repos/snyk/snyk-python-plugin/events","assignees_url":"https://api.github.com/repos/snyk/snyk-python-plugin/assignees{/user}","branches_url":"https://api.github.com/repos/snyk/snyk-python-plugin/branches{/branch}","tags_url":"https://api.github.com/repos/snyk/snyk-python-plugin/tags","blobs_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/refs{/sha}","trees_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/snyk/snyk-python-plugin/statuses/{sha}","languages_url":"https://api.github.com/repos/snyk/snyk-python-plugin/languages","stargazers_url":"https://api.github.com/repos/snyk/snyk-python-plugin/stargazers","contributors_url":"https://api.github.com/repos/snyk/snyk-python-plugin/contributors","subscribers_url":"https://api.github.com/repos/snyk/snyk-python-plugin/subscribers","subscription_url":"https://api.github.com/repos/snyk/snyk-python-plugin/subscription","commits_url":"https://api.github.com/repos/snyk/snyk-python-plugin/commits{/sha}","git_commits_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/commits{/sha}","comments_url":"https://api.github.com/repos/snyk/snyk-python-plugin/comments{/number}","issue_comment_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/comments{/number}","contents_url":"https://api.github.com/repos/snyk/snyk-python-plugin/contents/{+path}","compare_url":"https://api.github.com/repos/snyk/snyk-python-plugin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/snyk/snyk-python-plugin/merges","archive_url":"https://api.github.com/repos/snyk/snyk-python-plugin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/snyk/snyk-python-plugin/downloads","issues_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues{/number}","pulls_url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls{/number}","milestones_url":"https://api.github.com/repos/snyk/snyk-python-plugin/milestones{/number}","notifications_url":"https://api.github.com/repos/snyk/snyk-python-plugin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/snyk/snyk-python-plugin/labels{/name}","releases_url":"https://api.github.com/repos/snyk/snyk-python-plugin/releases{/id}","deployments_url":"https://api.github.com/repos/snyk/snyk-python-plugin/deployments","created_at":"2017-05-28T16:35:52Z","updated_at":"2018-06-20T06:33:29Z","pushed_at":"2018-07-18T14:41:56Z","git_url":"git://github.com/snyk/snyk-python-plugin.git","ssh_url":"git@github.com:snyk/snyk-python-plugin.git","clone_url":"https://github.com/snyk/snyk-python-plugin.git","svn_url":"https://github.com/snyk/snyk-python-plugin","homepage":null,"size":119,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3,"mirror_url":null,"archived":false,"open_issues_count":4,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":3,"open_issues":4,"watchers":2,"default_branch":"master"}},"base":{"label":"snyk:master","ref":"master","sha":"f01462ea6e7dcc08be9154a7c4ee40904ba98eaa","user":{"login":"snyk","id":12959162,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTU5MTYy","avatar_url":"https://avatars1.githubusercontent.com/u/12959162?v=4","gravatar_id":"","url":"https://api.github.com/users/snyk","html_url":"https://github.com/snyk","followers_url":"https://api.github.com/users/snyk/followers","following_url":"https://api.github.com/users/snyk/following{/other_user}","gists_url":"https://api.github.com/users/snyk/gists{/gist_id}","starred_url":"https://api.github.com/users/snyk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snyk/subscriptions","organizations_url":"https://api.github.com/users/snyk/orgs","repos_url":"https://api.github.com/users/snyk/repos","events_url":"https://api.github.com/users/snyk/events{/privacy}","received_events_url":"https://api.github.com/users/snyk/received_events","type":"Organization","site_admin":false},"repo":{"id":92673451,"node_id":"MDEwOlJlcG9zaXRvcnk5MjY3MzQ1MQ==","name":"snyk-python-plugin","full_name":"snyk/snyk-python-plugin","owner":{"login":"snyk","id":12959162,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEyOTU5MTYy","avatar_url":"https://avatars1.githubusercontent.com/u/12959162?v=4","gravatar_id":"","url":"https://api.github.com/users/snyk","html_url":"https://github.com/snyk","followers_url":"https://api.github.com/users/snyk/followers","following_url":"https://api.github.com/users/snyk/following{/other_user}","gists_url":"https://api.github.com/users/snyk/gists{/gist_id}","starred_url":"https://api.github.com/users/snyk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snyk/subscriptions","organizations_url":"https://api.github.com/users/snyk/orgs","repos_url":"https://api.github.com/users/snyk/repos","events_url":"https://api.github.com/users/snyk/events{/privacy}","received_events_url":"https://api.github.com/users/snyk/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/snyk/snyk-python-plugin","description":"Basic Snyk CLI plugin for Python support","fork":false,"url":"https://api.github.com/repos/snyk/snyk-python-plugin","forks_url":"https://api.github.com/repos/snyk/snyk-python-plugin/forks","keys_url":"https://api.github.com/repos/snyk/snyk-python-plugin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/snyk/snyk-python-plugin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/snyk/snyk-python-plugin/teams","hooks_url":"https://api.github.com/repos/snyk/snyk-python-plugin/hooks","issue_events_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/events{/number}","events_url":"https://api.github.com/repos/snyk/snyk-python-plugin/events","assignees_url":"https://api.github.com/repos/snyk/snyk-python-plugin/assignees{/user}","branches_url":"https://api.github.com/repos/snyk/snyk-python-plugin/branches{/branch}","tags_url":"https://api.github.com/repos/snyk/snyk-python-plugin/tags","blobs_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/refs{/sha}","trees_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/snyk/snyk-python-plugin/statuses/{sha}","languages_url":"https://api.github.com/repos/snyk/snyk-python-plugin/languages","stargazers_url":"https://api.github.com/repos/snyk/snyk-python-plugin/stargazers","contributors_url":"https://api.github.com/repos/snyk/snyk-python-plugin/contributors","subscribers_url":"https://api.github.com/repos/snyk/snyk-python-plugin/subscribers","subscription_url":"https://api.github.com/repos/snyk/snyk-python-plugin/subscription","commits_url":"https://api.github.com/repos/snyk/snyk-python-plugin/commits{/sha}","git_commits_url":"https://api.github.com/repos/snyk/snyk-python-plugin/git/commits{/sha}","comments_url":"https://api.github.com/repos/snyk/snyk-python-plugin/comments{/number}","issue_comment_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/comments{/number}","contents_url":"https://api.github.com/repos/snyk/snyk-python-plugin/contents/{+path}","compare_url":"https://api.github.com/repos/snyk/snyk-python-plugin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/snyk/snyk-python-plugin/merges","archive_url":"https://api.github.com/repos/snyk/snyk-python-plugin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/snyk/snyk-python-plugin/downloads","issues_url":"https://api.github.com/repos/snyk/snyk-python-plugin/issues{/number}","pulls_url":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls{/number}","milestones_url":"https://api.github.com/repos/snyk/snyk-python-plugin/milestones{/number}","notifications_url":"https://api.github.com/repos/snyk/snyk-python-plugin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/snyk/snyk-python-plugin/labels{/name}","releases_url":"https://api.github.com/repos/snyk/snyk-python-plugin/releases{/id}","deployments_url":"https://api.github.com/repos/snyk/snyk-python-plugin/deployments","created_at":"2017-05-28T16:35:52Z","updated_at":"2018-06-20T06:33:29Z","pushed_at":"2018-07-18T14:41:56Z","git_url":"git://github.com/snyk/snyk-python-plugin.git","ssh_url":"git@github.com:snyk/snyk-python-plugin.git","clone_url":"https://github.com/snyk/snyk-python-plugin.git","svn_url":"https://github.com/snyk/snyk-python-plugin","homepage":null,"size":119,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3,"mirror_url":null,"archived":false,"open_issues_count":4,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":3,"open_issues":4,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38"},"html":{"href":"https://github.com/snyk/snyk-python-plugin/pull/38"},"issue":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/38"},"comments":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/issues/38/comments"},"review_comments":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38/comments"},"review_comment":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/pulls/38/commits"},"statuses":{"href":"https://api.github.com/repos/snyk/snyk-python-plugin/statuses/d62f7e636d2cb88767b03926e513bf93e44afb3b"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"michael-go"}}