diff --git a/lib/datasource/pypi.js b/lib/datasource/pypi.js
index dd35de0932465b589d8c208a93d2c06c7818c8b6..04dd44c6a08c039659fe86809cd29daa824c584b 100644
--- a/lib/datasource/pypi.js
+++ b/lib/datasource/pypi.js
@@ -1,5 +1,5 @@
 const got = require('got');
-const { isVersion, sortVersions } = require('../versioning')('semver');
+const { isVersion, sortVersions } = require('../versioning')('pep440');
 
 module.exports = {
   getDependency,
@@ -19,7 +19,7 @@ async function getDependency(purl) {
     }
     if (dep.info && dep.info.home_page) {
       if (dep.info.home_page.startsWith('https://github.com')) {
-        dependency.repository_url = dep.info.home_page;
+        dependency.repositoryUrl = dep.info.home_page;
       } else {
         dependency.homepage = dep.info.home_page;
       }
diff --git a/lib/manager/pip_requirements/extract.js b/lib/manager/pip_requirements/extract.js
index ef5cc7489126c720347d2de5e221ae0bd23e54f0..b7283f4529d133f19285f2290255dd90934f75b4 100644
--- a/lib/manager/pip_requirements/extract.js
+++ b/lib/manager/pip_requirements/extract.js
@@ -1,7 +1,12 @@
-const XRegExp = require('xregexp');
 // based on https://www.python.org/dev/peps/pep-0508/#names
 const packagePattern = '[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]';
-const versionPattern = require('@renovate/pep440/lib/version').VERSION_PATTERN;
+const rangePattern = require('@renovate/pep440/lib/specifier').RANGE_PATTERN;
+
+const specifierPartPattern = `\\s*${rangePattern.replace(
+  /\?<\w+>/g,
+  '?:'
+)}\\s*`;
+const specifierPattern = `${specifierPartPattern}(?:,${specifierPartPattern})*`;
 
 module.exports = {
   packagePattern,
@@ -10,9 +15,8 @@ module.exports = {
 
 function extractDependencies(content) {
   logger.debug('pip_requirements.extractDependencies()');
-  // TODO: for now we only support package==version
-  // future support for more complex ranges could be added later
-  const regex = new XRegExp(`^(${packagePattern})(==${versionPattern})$`, 'g');
+
+  const regex = new RegExp(`^(${packagePattern})(${specifierPattern})$`, 'g');
   const deps = content
     .split('\n')
     .map((line, lineNumber) => {
diff --git a/lib/versioning/pep440/range.js b/lib/versioning/pep440/range.js
index dbfa449b2a84f1638a72d76e63a731fa8eae09d8..29c7a6b34944a4300b0c6f2df7aaeabeee8bdbe8 100644
--- a/lib/versioning/pep440/range.js
+++ b/lib/versioning/pep440/range.js
@@ -1,11 +1,128 @@
+const { gte, lte, satisfies } = require('@renovate/pep440');
+
+const { parse: parseVersion } = require('@renovate/pep440/lib/version');
+const { parse: parseRange } = require('@renovate/pep440/lib/specifier');
+
 module.exports = {
   getNewValue,
 };
 
+function getFutureVersion(baseVersion, toVersion, step) {
+  const toRelease = parseVersion(toVersion).release;
+  const baseRelease = parseVersion(baseVersion).release;
+  let found = false;
+  const futureRelease = baseRelease.map((basePart, index) => {
+    if (found) {
+      return 0;
+    }
+    const toPart = toRelease[index] || 0;
+    if (toPart > basePart) {
+      found = true;
+      return toPart + step;
+    }
+    return toPart;
+  });
+  if (!found) {
+    futureRelease[futureRelease.length - 1] += step;
+  }
+  return futureRelease.join('.');
+}
+
 function getNewValue(currentValue, rangeStrategy, fromVersion, toVersion) {
-  if (rangeStrategy === 'pin' || currentValue.startsWith('==')) {
+  // easy pin
+  if (rangeStrategy === 'pin') {
     return '==' + toVersion;
   }
-  logger.warn('Unsupported currentValue: ' + currentValue);
-  return toVersion;
+  const ranges = parseRange(currentValue);
+  if (!ranges) {
+    logger.warn('Invalid currentValue: ' + currentValue);
+    return null;
+  }
+  if (!ranges.length) {
+    // an empty string is an allowed value for PEP440 range
+    // it means get any version
+    logger.warn('Empty currentValue: ' + currentValue);
+    return currentValue;
+  }
+  if (rangeStrategy === 'replace') {
+    if (satisfies(toVersion, currentValue)) {
+      return currentValue;
+    }
+  }
+  if (!['replace', 'bump'].includes(rangeStrategy)) {
+    logger.warn('Unsupported rangeStrategy: ' + rangeStrategy);
+    return null;
+  }
+  if (ranges.some(range => range.operator === '===')) {
+    // the operator "===" is used for legacy non PEP440 versions
+    logger.warn('Arbitrary equality not supported: ' + currentValue);
+    return null;
+  }
+  const result = ranges
+    .map(range => {
+      // used to exclude versions,
+      // we assume that's for a good reason
+      if (range.operator === '!=') {
+        return range.operator + range.version;
+      }
+
+      // used to mark minimum supported version
+      if (['>', '>='].includes(range.operator)) {
+        if (lte(toVersion, range.version)) {
+          // this looks like a rollback
+          return '>=' + toVersion;
+        }
+        // this is similar to ~=
+        if (rangeStrategy === 'bump' && range.operator === '>=') {
+          return range.operator + toVersion;
+        }
+        // otherwise treat it same as exclude
+        return range.operator + range.version;
+      }
+
+      // this is used to exclude future versions
+      if (range.operator === '<') {
+        // if toVersion is that future version
+        if (gte(toVersion, range.version)) {
+          // now here things get tricky
+          // we calculate the new future version
+          const futureVersion = getFutureVersion(range.version, toVersion, 1);
+          return range.operator + futureVersion;
+        }
+        // otherwise treat it same as exclude
+        return range.operator + range.version;
+      }
+
+      // keep the .* suffix
+      if (range.prefix) {
+        const futureVersion = getFutureVersion(range.version, toVersion, 0);
+        return range.operator + futureVersion + '.*';
+      }
+
+      if (['==', '~=', '<='].includes(range.operator)) {
+        return range.operator + toVersion;
+      }
+
+      // unless PEP440 changes, this won't happen
+      // istanbul ignore next
+      logger.error(
+        { toVersion, currentValue, range },
+        'pep440: failed to process range'
+      );
+      // istanbul ignore next
+      return null;
+    })
+    .filter(Boolean)
+    .join(', ');
+
+  if (!satisfies(toVersion, result)) {
+    // we failed at creating the range
+    logger.error(
+      { result, toVersion, currentValue },
+      'pep440: failed to calcuate newValue'
+    );
+    return null;
+  }
+
+  return result;
 }
diff --git a/package.json b/package.json
index 6267ef63f095bcdd3063a3a27b1c1486e81be7a3..5870437328df7ec552348cf5ed739214bd7fe2f6 100644
--- a/package.json
+++ b/package.json
@@ -107,7 +107,6 @@
     "upath": "1.1.0",
     "validator": "10.3.0",
     "vso-node-api": "6.5.0",
-    "xregexp": "4.2.0",
     "yarn": "1.7.0"
   },
   "devDependencies": {
diff --git a/test/datasource/__snapshots__/pypi.spec.js.snap b/test/datasource/__snapshots__/pypi.spec.js.snap
index bced52c0d137984f2153137d848575609b3c5163..df0949e1fe7d7059c83de3af51c13e39e2234beb 100644
--- a/test/datasource/__snapshots__/pypi.spec.js.snap
+++ b/test/datasource/__snapshots__/pypi.spec.js.snap
@@ -92,7 +92,7 @@ Object {
       "version": "0.1.7",
     },
   ],
-  "repository_url": "https://github.com/Azure/azure-cli",
+  "repositoryUrl": "https://github.com/Azure/azure-cli",
 }
 `;
 
diff --git a/test/versioning/pep440.spec.js b/test/versioning/pep440.spec.js
index 83998bb82fc22d8555ebcdd9ea9759499b8e5b63..e7f877414ab8b74e80628dec7f2fb49c5239ff2d 100644
--- a/test/versioning/pep440.spec.js
+++ b/test/versioning/pep440.spec.js
@@ -79,14 +79,64 @@ describe('pep440.minSatisfyingVersion(versions, range)', () => {
 });
 
 describe('pep440.getNewValue()', () => {
-  it('returns double equals', () => {
-    expect(pep440.getNewValue('==1.0.0', 'replace', '1.0.0', '1.0.1')).toBe(
-      '==1.0.1'
-    );
-  });
-  it('returns version', () => {
-    expect(pep440.getNewValue('>=1.0.0', 'replace', '1.0.0', '1.0.1')).toBe(
-      '1.0.1'
-    );
+  const { getNewValue } = pep440;
+
+  // cases: [currentValue, expectedBump]
+  [
+    // simple cases
+    ['==1.0.3', '==1.2.3'],
+    ['>=1.2.0', '>=1.2.3'],
+    ['~=1.2.0', '~=1.2.3'],
+    ['~=1.0.3', '~=1.2.3'],
+
+    // glob
+    ['==1.2.*', '==1.2.*'],
+    ['==1.0.*', '==1.2.*'],
+
+    // future versions guard
+    ['<1.2.2.3', '<1.2.4.0'],
+    ['<1.2.3', '<1.2.4'],
+    ['<1.2', '<1.3'],
+    ['<1', '<2'],
+    ['<2.0.0', '<2.0.0'],
+
+    // minimum version guard
+    ['>0.9.8', '>0.9.8'],
+    // rollback
+    ['>2.0.0', '>=1.2.3'],
+    ['>=2.0.0', '>=1.2.3'],
+
+    // complex ranges
+    ['~=1.1.0, !=1.1.1', '~=1.2.3, !=1.1.1'],
+
+    // invalid & not supported
+    [' ', ' '],
+    ['invalid', null],
+    ['===1.0.3', null],
+    // impossible
+    ['!=1.2.3', null],
+  ].forEach(([currentValue, expectedBump]) => {
+    const bumped = getNewValue(currentValue, 'bump', '1.0.0', '1.2.3');
+    it(`bumps '${currentValue}' to '${expectedBump}'`, () => {
+      expect(bumped).toBe(expectedBump);
+    });
+
+    const replaced = getNewValue(currentValue, 'replace', '1.0.0', '1.2.3');
+    const needReplace = pep440.matches('1.2.3', currentValue);
+    const expectedReplace = needReplace ? currentValue : bumped;
+    it(`replaces '${currentValue}' to '${expectedReplace}'`, () => {
+      expect(replaced).toBe(expectedReplace);
+    });
+
+    const pinned = getNewValue(currentValue, 'pin', '1.0.0', '1.2.3');
+    const expectedPin = '==1.2.3';
+    it(`pins '${currentValue}' to '${expectedPin}'`, () => {
+      expect(pinned).toBe(expectedPin);
+    });
+  });
+
+  it('guards against unsupported rangeStrategy', () => {
+    const invalid = getNewValue('==1.2.3', 'invalid', '1.0.0', '1.2.3');
+    expect(invalid).toBe(null);
   });
 });
diff --git a/yarn.lock b/yarn.lock
index 52fab85ec47c1f122bb28e711ad787bbda98b70b..379e56d93dbfd84baf208c72e170d6c9af1966b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6615,10 +6615,6 @@ xml-name-validator@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
 
-xregexp@4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.2.0.tgz#33f09542b0d7cabed46728eeacac4d5bd764ccf5"
-
 xregexp@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.1.1.tgz#eb8a032aa028d403f7b1b22c47a5f16c24b21d8d"