diff --git a/lib/versioning/index.js b/lib/versioning/index.js
index f08f9862e6ad811af7613b5813c6e62f98427141..1619f98b56d29e141d7ef2fb0b450a81c00b005c 100644
--- a/lib/versioning/index.js
+++ b/lib/versioning/index.js
@@ -1,7 +1,9 @@
 const semver = require('./semver');
+const pep440 = require('./pep440');
 
 const schemes = {
   semver,
+  pep440,
 };
 
 module.exports = function getVersionScheme(versionScheme) {
diff --git a/lib/versioning/pep440/index.js b/lib/versioning/pep440/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..323a7adae70e0d9cd7e77b99f121d402d886f1be
--- /dev/null
+++ b/lib/versioning/pep440/index.js
@@ -0,0 +1,55 @@
+const pep440 = require('@renovate/pep440');
+const { filter } = require('@renovate/pep440/lib/specifier');
+const { rangify } = require('./range');
+const semantic = require('./semantic');
+
+const {
+  compare: sortVersions,
+  satisfies: matches,
+  valid: isPinnedVersion,
+  validRange,
+  explain,
+  gt: isGreaterThan,
+  eq: equals,
+} = pep440;
+
+const { major: getMajor, minor: getMinor } = semantic;
+
+const isStable = input => {
+  const version = explain(input);
+  if (!version) {
+    return false;
+  }
+  return !version.is_prerelease;
+};
+
+const isRange = input => isValid(input) && !isPinnedVersion(input);
+
+// If this is left as an alias, inputs like "17.04.0" throw errors
+const isValid = input => validRange(input);
+
+const maxSatisfyingVersion = (versions, range) => {
+  const found = filter(versions, range).sort(sortVersions);
+  return found.length === 0 ? null : found[found.length - 1];
+};
+
+const minSatisfyingVersion = (versions, range) => {
+  const found = filter(versions, range).sort(sortVersions);
+  return found.length === 0 ? null : found[0];
+};
+
+module.exports = {
+  equals,
+  getMajor,
+  getMinor,
+  isGreaterThan,
+  isPinnedVersion,
+  isRange,
+  isStable,
+  isValid,
+  matches,
+  maxSatisfyingVersion,
+  minSatisfyingVersion,
+  rangify,
+  sortVersions,
+};
diff --git a/lib/versioning/pep440/range.js b/lib/versioning/pep440/range.js
new file mode 100644
index 0000000000000000000000000000000000000000..878b657f824227f545946a0c4ad8f0521bd24dac
--- /dev/null
+++ b/lib/versioning/pep440/range.js
@@ -0,0 +1,13 @@
+module.exports = {
+  rangify,
+};
+
+function rangify(config, currentVersion, fromVersion, toVersion) {
+  const { rangeStrategy } = config;
+  // istanbul ignore if
+  if (rangeStrategy !== 'pin') {
+    logger.warn({ rangeStrategy }, 'Unsupported rangeStrategy');
+    return null;
+  }
+  return toVersion;
+}
diff --git a/lib/versioning/pep440/semantic.js b/lib/versioning/pep440/semantic.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f23ee2a7298161180f25494fdd3084f63e492af
--- /dev/null
+++ b/lib/versioning/pep440/semantic.js
@@ -0,0 +1,26 @@
+const pep440 = require('@renovate/pep440');
+
+const major = input => {
+  const version = pep440.explain(input);
+  if (!version) {
+    throw new TypeError('Invalid Version: ' + input);
+  }
+  return version.release[0];
+};
+
+const minor = input => {
+  const version = pep440.explain(input);
+  if (!version) {
+    throw new TypeError('Invalid Version: ' + input);
+  }
+  if (version.release.length < 2) {
+    return 0;
+  }
+  return version.release[1];
+};
+
+// those notation are borrowed from semver, not sure if should be moved to @renovate/pep440
+module.exports = {
+  major,
+  minor,
+};
diff --git a/package.json b/package.json
index 705f0dacb898f894692a64d168bfb46965421e89..6b79b3e818ae44f9186db38877621e36ac36015d 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
     "openpgp": "2.6.2",
     "p-all": "1.0.0",
     "parse-link-header": "1.0.1",
+    "@renovate/pep440": "0.1.1",
     "pnpm": "2.2.2",
     "registry-auth-token": "3.3.2",
     "root-require": "0.3.1",
diff --git a/test/versioning/index.spec.js b/test/versioning/index.spec.js
index 8dcfd0eb588d2eda9ab75fa731897f647be319de..9e7fb786b710146f6b3e02f96f30672fbbca1166 100644
--- a/test/versioning/index.spec.js
+++ b/test/versioning/index.spec.js
@@ -5,4 +5,12 @@ describe('versioning(versionScheme)', () => {
     expect(versioning(undefined)).toBe(versioning('semver'));
     expect(versioning('unknown')).toBe(versioning('semver'));
   });
+
+  it('should return the same interface', () => {
+    const semverApi = Object.keys(versioning('semver'));
+    const pep440Api = Object.keys(versioning('pep440')).concat(
+      'isLessThanRange'
+    );
+    expect(pep440Api.sort()).toEqual(semverApi.sort());
+  });
 });
diff --git a/test/versioning/pep440.spec.js b/test/versioning/pep440.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab1637d226816a2177012705d5802d3158e4c537
--- /dev/null
+++ b/test/versioning/pep440.spec.js
@@ -0,0 +1,111 @@
+const pep440 = require('../../lib/versioning')('pep440');
+
+describe('pep440.isValid(input)', () => {
+  it('should return null for irregular versions', () => {
+    expect(!!pep440.isValid('17.04.0')).toBe(false);
+  });
+  it('should support simple pep440', () => {
+    expect(!!pep440.isValid('==1.2.3')).toBe(true);
+  });
+  it('should support pep440 with RC', () => {
+    expect(!!pep440.isValid('==1.2.3rc0')).toBe(true);
+  });
+  it('should support ranges', () => {
+    expect(!!pep440.isValid('~=1.2.3')).toBe(true);
+    expect(!!pep440.isValid('==1.2.*')).toBe(true);
+    expect(!!pep440.isValid('>1.2.3')).toBe(true);
+  });
+  it('should reject github repositories', () => {
+    expect(!!pep440.isValid('renovateapp/renovate')).toBe(false);
+    expect(!!pep440.isValid('renovateapp/renovate#master')).toBe(false);
+    expect(
+      !!pep440.isValid('https://github.com/renovateapp/renovate.git')
+    ).toBe(false);
+  });
+});
+describe('pep440.isRange(input)', () => {
+  it('rejects simple pep440', () => {
+    expect(!!pep440.isRange('1.2.3')).toBe(false);
+  });
+  it('accepts tilde', () => {
+    expect(!!pep440.isRange('~=1.2.3')).toBe(true);
+  });
+  it('accepts glob', () => {
+    expect(!!pep440.isRange('==1.2.*')).toBe(true);
+  });
+});
+
+describe('pep440.getMinor(input)', () => {
+  it('returns correct value', () => {
+    expect(pep440.getMinor('1.2.3')).toBe(2);
+  });
+  it('pads zeros', () => {
+    expect(pep440.getMinor('1')).toBe(0);
+  });
+  it('throws when version invalid', () => {
+    expect(() => pep440.getMinor('not_version')).toThrowError(TypeError);
+  });
+});
+
+describe('pep440.getMajor(version)', () => {
+  it('returns correct value', () => {
+    expect(pep440.getMajor('1.2.3')).toBe(1);
+  });
+  it('handles epoch', () => {
+    expect(pep440.getMajor('25!1.2.3')).toBe(1);
+  });
+  it('throws when version invalid', () => {
+    expect(() => pep440.getMajor('not_version')).toThrowError(TypeError);
+  });
+});
+
+describe('pep440.getMajor(version)', () => {
+  it('returns correct value', () => {
+    expect(pep440.getMajor('1.2.3')).toBe(1);
+  });
+  it('handles epoch', () => {
+    expect(pep440.getMajor('25!1.2.3')).toBe(1);
+  });
+  it('throws when version invalid', () => {
+    expect(() => pep440.getMajor('not_version')).toThrowError(TypeError);
+  });
+});
+
+describe('pep440.isStable(version)', () => {
+  it('returns correct value', () => {
+    expect(pep440.isStable('1.2.3')).toBe(true);
+    expect(pep440.isStable('1.2.3rc0')).toBe(false);
+  });
+  it('returns false when version invalid', () => {
+    expect(pep440.isStable('not_version')).toBe(false);
+  });
+});
+
+const versions = [
+  '0.9.4',
+  '1.0.0',
+  '1.1.5',
+  '1.2.1',
+  '1.2.2',
+  '1.2.3',
+  '1.3.4',
+  '2.0.3',
+];
+
+describe('pep440.maxSatisfyingVersion(versions, range)', () => {
+  it('returns correct value', () => {
+    expect(pep440.maxSatisfyingVersion(versions, '~=1.2.1')).toBe('1.2.3');
+  });
+  it('returns null when none found', () => {
+    expect(pep440.maxSatisfyingVersion(versions, '~=2.1')).toBe(null);
+  });
+});
+
+describe('pep440.minSatisfyingVersion(versions, range)', () => {
+  it('returns correct value', () => {
+    expect(pep440.minSatisfyingVersion(versions, '~=1.2.1')).toBe('1.2.1');
+  });
+  it('returns null when none found', () => {
+    expect(pep440.minSatisfyingVersion(versions, '~=2.1')).toBe(null);
+  });
+});
diff --git a/yarn.lock b/yarn.lock
index 6d09871913c351a932be2fb474fe47a10f2a3270..2fb10aa11e00a6091a392add04debfa294325f9f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -30,6 +30,12 @@
     node-fetch "^2.1.1"
     url-template "^2.0.8"
 
+"@renovate/pep440@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@renovate/pep440/-/pep440-0.1.1.tgz#1f52f35a3fb536b184376059b547a1f48cdf95a5"
+  dependencies:
+    xregexp "^4.1.1"
+
 "@semantic-release/commit-analyzer@^5.0.0":
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-5.0.0.tgz#767a2055b5cd0a67421b1d504f3ca7db97055c42"
@@ -7072,6 +7078,10 @@ xml-name-validator@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
 
+xregexp@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.1.1.tgz#eb8a032aa028d403f7b1b22c47a5f16c24b21d8d"
+
 xtend@~4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"