diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 35c29c5ddf8d414b8d9a15baca1318010bc0cd2c..f1501964130df2ee4d25427152fa752e86125296 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -387,6 +387,7 @@ const options = [
       'node',
       'npm',
       'pep440',
+      'poetry',
       'ruby',
       'semver',
     ],
@@ -1441,6 +1442,19 @@ const options = [
     mergeable: true,
     cli: false,
   },
+  {
+    name: 'poetry',
+    releaseStatus: 'beta',
+    description: 'Configuration object for pyproject.toml files',
+    stage: 'package',
+    type: 'toml',
+    default: {
+      enabled: false,
+      versionScheme: 'poetry',
+      fileMatch: ['(^|/)pyproject\\.toml$'],
+    },
+    mergeable: true,
+  },
   {
     name: 'python',
     description: 'Configuration object for python',
diff --git a/lib/manager/index.js b/lib/manager/index.js
index bf695e247cf47bf5af3dffe7d97986114ae4ddaa..daef001a2ae274add8171f60e99c0ff23348e76c 100644
--- a/lib/manager/index.js
+++ b/lib/manager/index.js
@@ -22,6 +22,7 @@ const managerList = [
   'pip_requirements',
   'pip_setup',
   'pipenv',
+  'poetry',
   'terraform',
   'travis',
 ];
diff --git a/lib/manager/poetry/artifacts.js b/lib/manager/poetry/artifacts.js
new file mode 100644
index 0000000000000000000000000000000000000000..f54b7f92b35bb8e7916f5e53cc30c53ac1503524
--- /dev/null
+++ b/lib/manager/poetry/artifacts.js
@@ -0,0 +1,114 @@
+const upath = require('upath');
+const process = require('process');
+const fs = require('fs-extra');
+const { exec } = require('child-process-promise');
+
+module.exports = {
+  getArtifacts,
+};
+
+async function getArtifacts(
+  packageFileName,
+  updatedDeps,
+  newPackageFileContent,
+  config
+) {
+  await logger.debug(`poetry.getArtifacts(${packageFileName})`);
+  if (updatedDeps === undefined || updatedDeps.length < 1) {
+    logger.debug('No updated poetry deps - returning null');
+    return null;
+  }
+  const lockFileName = 'poetry.lock';
+  let existingLockFileContent = await platform.getFile(lockFileName);
+  let oldLockFileName;
+  if (!existingLockFileContent) {
+    oldLockFileName = 'pyproject.lock';
+    existingLockFileContent = await platform.getFile(oldLockFileName);
+    // istanbul ignore if
+    if (existingLockFileContent) {
+      logger.info(`${oldLockFileName} found`);
+    } else {
+      logger.debug(`No ${lockFileName} found`);
+      return null;
+    }
+  }
+  const localPackageFileName = upath.join(config.localDir, packageFileName);
+  const localLockFileName = upath.join(config.localDir, lockFileName);
+  let stdout;
+  let stderr;
+  const startTime = process.hrtime();
+  try {
+    await fs.outputFile(localPackageFileName, newPackageFileContent);
+    logger.debug(`Updating ${lockFileName}`);
+    const cwd = config.localDir;
+    const env =
+      global.trustLevel === 'high'
+        ? process.env
+        : {
+            HOME: process.env.HOME,
+            PATH: process.env.PATH,
+          };
+    let cmd;
+    // istanbul ignore if
+    if (config.binarySource === 'docker') {
+      logger.info('Running poetry via docker');
+      cmd = `docker run --rm `;
+      const volumes = [cwd];
+      cmd += volumes.map(v => `-v ${v}:${v} `).join('');
+      const envVars = [];
+      cmd += envVars.map(e => `-e ${e} `);
+      cmd += `-w ${cwd} `;
+      cmd += `renovate/poetry poetry`;
+    } else {
+      logger.info('Running poetry via global poetry');
+      cmd = 'poetry';
+    }
+    for (let i = 0; i < updatedDeps.length; i += 1) {
+      const dep = updatedDeps[i];
+      cmd += ` update --lock --no-interaction ${dep}`;
+      ({ stdout, stderr } = await exec(cmd, {
+        cwd,
+        shell: true,
+        env,
+      }));
+    }
+    const duration = process.hrtime(startTime);
+    const seconds = Math.round(duration[0] + duration[1] / 1e9);
+    logger.info(
+      { seconds, type: `${lockFileName}`, stdout, stderr },
+      'Updated lockfile'
+    );
+    logger.debug(`Returning updated ${lockFileName}`);
+    const newPoetryLockContent = await fs.readFile(localLockFileName, 'utf8');
+    if (existingLockFileContent === newPoetryLockContent) {
+      logger.debug(`${lockFileName} is unchanged`);
+      return null;
+    }
+    let fileName;
+    // istanbul ignore if
+    if (oldLockFileName) {
+      fileName = oldLockFileName;
+    } else {
+      fileName = lockFileName;
+    }
+    return [
+      {
+        file: {
+          name: fileName,
+          contents: newPoetryLockContent,
+        },
+      },
+    ];
+  } catch (err) {
+    logger.warn(
+      { err, message: err.message },
+      `Failed to update ${lockFileName} file`
+    );
+    return {
+      lockFileError: {
+        lockFile: lockFileName,
+        stderr: err.message,
+      },
+    };
+  }
+}
diff --git a/lib/manager/poetry/extract.js b/lib/manager/poetry/extract.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a49e9f703ca1614dc12bf0e0a3f3a899b7ed90e
--- /dev/null
+++ b/lib/manager/poetry/extract.js
@@ -0,0 +1,77 @@
+const toml = require('toml');
+const semver = require('../../versioning/poetry');
+
+module.exports = {
+  extractPackageFile,
+};
+
+function extractPackageFile(content, fileName) {
+  logger.trace(`poetry.extractPackageFile(${fileName})`);
+  let pyprojectfile;
+  try {
+    pyprojectfile = toml.parse(content);
+  } catch (err) {
+    logger.debug({ err }, 'Error parsing pyproject.toml file');
+    return null;
+  }
+  const deps = [
+    ...extractFromSection(pyprojectfile, 'dependencies'),
+    ...extractFromSection(pyprojectfile, 'dev-dependencies'),
+    ...extractFromSection(pyprojectfile, 'extras'),
+  ];
+  if (!deps.length) {
+    return null;
+  }
+  return { deps };
+}
+
+function extractFromSection(parsedFile, section) {
+  const deps = [];
+  const sectionContent = parsedFile.tool.poetry[section];
+  if (!sectionContent) {
+    return [];
+  }
+  Object.keys(sectionContent).forEach(depName => {
+    let skipReason;
+    let currentValue = sectionContent[depName];
+    let nestedVersion = false;
+    if (typeof currentValue !== 'string') {
+      const version = sectionContent[depName].version;
+      const path = sectionContent[depName].path;
+      const git = sectionContent[depName].git;
+      if (version) {
+        currentValue = version;
+        nestedVersion = true;
+        if (path) {
+          skipReason = 'path-dependency';
+        }
+        if (git) {
+          skipReason = 'git-dependency';
+        }
+      } else if (path) {
+        currentValue = '';
+        skipReason = 'path-dependency';
+      } else if (git) {
+        currentValue = '';
+        skipReason = 'git-dependency';
+      } else {
+        currentValue = '';
+        skipReason = 'multiple-constraint-dep';
+      }
+    }
+    const dep = {
+      depName,
+      depType: section,
+      currentValue,
+      nestedVersion,
+      datasource: 'pypi',
+    };
+    if (skipReason) {
+      dep.skipReason = skipReason;
+    } else if (!semver.isValid(dep.currentValue)) {
+      dep.skipReason = 'unknown-version';
+    }
+    deps.push(dep);
+  });
+  return deps;
+}
diff --git a/lib/manager/poetry/index.js b/lib/manager/poetry/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2a396b6f433ab857aae8d6fe4ccab31b067d93c
--- /dev/null
+++ b/lib/manager/poetry/index.js
@@ -0,0 +1,14 @@
+const { extractPackageFile } = require('./extract');
+const { updateDependency } = require('./update');
+const { getArtifacts } = require('./artifacts');
+
+const language = 'python';
+
+module.exports = {
+  extractPackageFile,
+  getArtifacts,
+  language,
+  updateDependency,
+  // TODO: Support this
+  supportsLockFileMaintenance: false,
+};
diff --git a/lib/manager/poetry/update.js b/lib/manager/poetry/update.js
new file mode 100644
index 0000000000000000000000000000000000000000..1536899a858431f82a5bffc2b563d0386411092b
--- /dev/null
+++ b/lib/manager/poetry/update.js
@@ -0,0 +1,93 @@
+const _ = require('lodash');
+const toml = require('toml');
+
+module.exports = {
+  updateDependency,
+};
+
+// TODO: Maybe factor out common code from pipenv.updateDependency and poetry.updateDependency
+// Return true if the match string is found at index in content
+function matchAt(content, index, match) {
+  return content.substring(index, index + match.length) === match;
+}
+
+// Replace oldString with newString at location index of content
+function replaceAt(content, index, oldString, newString) {
+  logger.debug(`Replacing ${oldString} with ${newString} at index ${index}`);
+  return (
+    content.substr(0, index) +
+    newString +
+    content.substr(index + oldString.length)
+  );
+}
+
+function updateDependency(fileContent, upgrade) {
+  logger.trace({ config: upgrade }, 'poetry.updateDependency()');
+  if (!upgrade) {
+    return null;
+  }
+  const { depType, depName, newValue, nestedVersion } = upgrade;
+  const parsedContents = toml.parse(fileContent);
+  if (!parsedContents.tool.poetry[depType]) {
+    logger.info(
+      { config: upgrade },
+      `Error: Section tool.poetry.${depType} doesn't exist in pyproject.toml file, update failed`
+    );
+    return null;
+  }
+  let oldVersion;
+  if (nestedVersion) {
+    const oldDep = parsedContents.tool.poetry[depType][depName];
+    if (!oldDep) {
+      logger.info(
+        { config: upgrade },
+        `Could not get version of dependency ${depType}, update failed (most likely name is invalid)`
+      );
+      return null;
+    }
+    oldVersion = oldDep.version;
+  } else {
+    oldVersion = parsedContents.tool.poetry[depType][depName];
+  }
+  if (!oldVersion) {
+    logger.info(
+      { config: upgrade },
+      `Could not get version of dependency ${depType}, update failed (most likely name is invalid)`
+    );
+    return null;
+  }
+  if (oldVersion === newValue) {
+    logger.info('Version is already updated');
+    return fileContent;
+  }
+  if (nestedVersion) {
+    parsedContents.tool.poetry[depType][depName].version = newValue;
+  } else {
+    parsedContents.tool.poetry[depType][depName] = newValue;
+  }
+  const searchString = `"${oldVersion}"`;
+  const newString = `"${newValue}"`;
+  let newFileContent = null;
+  let searchIndex = fileContent.indexOf(`[${depType}]`) + depType.length;
+  for (; searchIndex < fileContent.length; searchIndex += 1) {
+    // First check if we have a hit for the old version
+    if (matchAt(fileContent, searchIndex, searchString)) {
+      logger.trace(`Found match at index ${searchIndex}`);
+      // Now test if the result matches
+      const testContent = replaceAt(
+        fileContent,
+        searchIndex,
+        searchString,
+        newString
+      );
+      // Compare the parsed toml structure of old and new
+      if (_.isEqual(parsedContents, toml.parse(testContent))) {
+        newFileContent = testContent;
+        break;
+      } else {
+        logger.debug('Mismatched replace at searchIndex ' + searchIndex);
+      }
+    }
+  }
+  return newFileContent;
+}
diff --git a/lib/versioning/poetry/index.js b/lib/versioning/poetry/index.js
index 74fcdeaa1094e644f926033e1c8e51b1902af468..b073703b95dbd7d172cd14c7a945e41362508827 100644
--- a/lib/versioning/poetry/index.js
+++ b/lib/versioning/poetry/index.js
@@ -1,3 +1,5 @@
+const { parseRange } = require('semver-utils');
+const { major, minor } = require('semver');
 const npm = require('../npm');
 
 function notEmpty(s) {
@@ -60,6 +62,25 @@ const isSingleVersion = constraint =>
   isVersion(constraint.trim());
 
 function getNewValue(currentValue, rangeStrategy, fromVersion, toVersion) {
+  if (rangeStrategy === 'replace') {
+    const npmCurrentValue = poetry2npm(currentValue);
+    const parsedRange = parseRange(npmCurrentValue);
+    const element = parsedRange[parsedRange.length - 1];
+    if (parsedRange.length === 1 && element.operator) {
+      if (element.operator === '^') {
+        const version = handleShort('^', npmCurrentValue, toVersion);
+        if (version) {
+          return npm2poetry(version);
+        }
+      }
+      if (element.operator === '~') {
+        const version = handleShort('~', npmCurrentValue, toVersion);
+        if (version) {
+          return npm2poetry(version);
+        }
+      }
+    }
+  }
   const newSemver = npm.getNewValue(
     poetry2npm(currentValue),
     rangeStrategy,
@@ -70,6 +91,21 @@ function getNewValue(currentValue, rangeStrategy, fromVersion, toVersion) {
   return newPoetry;
 }
 
+function handleShort(operator, currentValue, toVersion) {
+  const toVersionMajor = major(toVersion);
+  const toVersionMinor = minor(toVersion);
+  const split = currentValue.split('.');
+  if (split.length === 1) {
+    // [^,~]4
+    return operator + toVersionMajor;
+  }
+  if (split.length === 2) {
+    // [^,~]4.1
+    return operator + toVersionMajor + '.' + toVersionMinor;
+  }
+  return null;
+}
+
 module.exports = {
   ...npm,
   getNewValue,
diff --git a/renovate-schema.json b/renovate-schema.json
index da4d1488c251af32cebee6c18e9129e687266530..3d6bd6eaa1a35a56423ef0b2db83f36519ac4da5 100644
--- a/renovate-schema.json
+++ b/renovate-schema.json
@@ -252,6 +252,7 @@
         "node",
         "npm",
         "pep440",
+        "poetry",
         "ruby",
         "semver"
       ],
@@ -986,6 +987,15 @@
       },
       "$ref": "#"
     },
+    "poetry": {
+      "description": "Configuration object for pyproject.toml files",
+      "type": "toml",
+      "default": {
+        "enabled": false,
+        "versionScheme": "poetry",
+        "fileMatch": ["(^|/)pyproject\\.toml$"]
+      }
+    },
     "python": {
       "description": "Configuration object for python",
       "type": "object",
diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap
index be7a0b6ec6df55a0e83c75c1a9a644f7f694a9c3..2584cf380aa29958fda8d1f79a798814f13baa3d 100644
--- a/test/config/__snapshots__/validation.spec.js.snap
+++ b/test/config/__snapshots__/validation.spec.js.snap
@@ -87,7 +87,7 @@ Array [
     "depName": "Configuration Error",
     "message": "packageRules:
         You have included an unsupported manager in a package rule. Your list: foo. 
-        Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, docker-compose, dockerfile, github-actions, gitlabci, gomod, gradle, gradle-wrapper, kubernetes, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, terraform, travis).",
+        Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, docker-compose, dockerfile, github-actions, gitlabci, gomod, gradle, gradle-wrapper, kubernetes, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, terraform, travis).",
   },
 ]
 `;
diff --git a/test/manager/poetry/__snapshots__/artifacts.spec.js.snap b/test/manager/poetry/__snapshots__/artifacts.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..46da37a1ed90ef60caf5bc1efde537b822abc526
--- /dev/null
+++ b/test/manager/poetry/__snapshots__/artifacts.spec.js.snap
@@ -0,0 +1,10 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`.getArtifacts() catches errors 1`] = `
+Object {
+  "lockFileError": Object {
+    "lockFile": "poetry.lock",
+    "stderr": "not found",
+  },
+}
+`;
diff --git a/test/manager/poetry/__snapshots__/extract.spec.js.snap b/test/manager/poetry/__snapshots__/extract.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..8635a124169f7593299d529a320ae5221e2cc566
--- /dev/null
+++ b/test/manager/poetry/__snapshots__/extract.spec.js.snap
@@ -0,0 +1,139 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`lib/manager/poetry/extract extractPackageFile() extracts multiple dependencies (with dep = {version = "1.2.3"} case) 1`] = `
+Array [
+  Object {
+    "currentValue": "*",
+    "datasource": "pypi",
+    "depName": "dep1",
+    "depType": "dependencies",
+    "nestedVersion": true,
+  },
+  Object {
+    "currentValue": "^0.6.0",
+    "datasource": "pypi",
+    "depName": "dep2",
+    "depType": "dependencies",
+    "nestedVersion": true,
+  },
+  Object {
+    "currentValue": "^0.33.6",
+    "datasource": "pypi",
+    "depName": "dep3",
+    "depType": "dependencies",
+    "nestedVersion": true,
+    "skipReason": "path-dependency",
+  },
+  Object {
+    "currentValue": "",
+    "datasource": "pypi",
+    "depName": "dep4",
+    "depType": "dependencies",
+    "nestedVersion": false,
+    "skipReason": "path-dependency",
+  },
+  Object {
+    "currentValue": "^0.8.3",
+    "datasource": "pypi",
+    "depName": "extra_dep1",
+    "depType": "extras",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^0.9.4",
+    "datasource": "pypi",
+    "depName": "extra_dep2",
+    "depType": "extras",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^0.4.0",
+    "datasource": "pypi",
+    "depName": "extra_dep3",
+    "depType": "extras",
+    "nestedVersion": false,
+  },
+]
+`;
+
+exports[`lib/manager/poetry/extract extractPackageFile() extracts multiple dependencies 1`] = `
+Array [
+  Object {
+    "currentValue": "0.0.0",
+    "datasource": "pypi",
+    "depName": "dep1_",
+    "depType": "dependencies",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "0.0.0",
+    "datasource": "pypi",
+    "depName": "dep1",
+    "depType": "dependencies",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^0.6.0",
+    "datasource": "pypi",
+    "depName": "dep2",
+    "depType": "dependencies",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^0.33.6",
+    "datasource": "pypi",
+    "depName": "dep3",
+    "depType": "dependencies",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^3.0",
+    "datasource": "pypi",
+    "depName": "dev_dep1",
+    "depType": "dev-dependencies",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "Invalid version.",
+    "datasource": "pypi",
+    "depName": "dev_dep2",
+    "depType": "dev-dependencies",
+    "nestedVersion": false,
+    "skipReason": "unknown-version",
+  },
+  Object {
+    "currentValue": "^0.8.3",
+    "datasource": "pypi",
+    "depName": "extra_dep1",
+    "depType": "extras",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^0.9.4",
+    "datasource": "pypi",
+    "depName": "extra_dep2",
+    "depType": "extras",
+    "nestedVersion": false,
+  },
+  Object {
+    "currentValue": "^0.4.0",
+    "datasource": "pypi",
+    "depName": "extra_dep3",
+    "depType": "extras",
+    "nestedVersion": false,
+  },
+]
+`;
+
+exports[`lib/manager/poetry/extract extractPackageFile() handles multiple constraint dependencies 1`] = `
+Array [
+  Object {
+    "currentValue": "",
+    "datasource": "pypi",
+    "depName": "foo",
+    "depType": "dependencies",
+    "nestedVersion": false,
+    "skipReason": "multiple-constraint-dep",
+  },
+]
+`;
diff --git a/test/manager/poetry/__snapshots__/update.spec.js.snap b/test/manager/poetry/__snapshots__/update.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..fc43034e3fb8133ab2c43b58ba6f56d80d3add1c
--- /dev/null
+++ b/test/manager/poetry/__snapshots__/update.spec.js.snap
@@ -0,0 +1,108 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`manager/poetry/update updateDependency replaces existing value 1`] = `
+"[tool.poetry]
+name = \\"example 1\\"
+version = \\"0.1.0\\"
+description = \\"\\"
+authors = [\\"John Doe <john.doe@gmail.com>\\"]
+
+[tool.poetry.dependencies]
+dep1_ = \\"0.0.0\\"
+dep1 = \\"1.0.0\\"
+dep2 = \\"^0.6.0\\"
+dep3 = \\"^0.33.6\\"
+
+[tool.poetry.extras]
+extra_dep1 = \\"^0.8.3\\"
+extra_dep2 = \\"^0.9.4\\"
+extra_dep3 = \\"^0.4.0\\"
+
+[tool.poetry.dev-dependencies]
+dev_dep1 = \\"^3.0\\"
+dev_dep2 = \\"Invalid version.\\""
+`;
+
+exports[`manager/poetry/update updateDependency replaces nested value 1`] = `
+"[tool.poetry]
+name = \\"example 2\\"
+version = \\"0.1.0\\"
+description = \\"\\"
+authors = [\\"John Doe <john.doe@gmail.com>\\"]
+
+[tool.poetry.dependencies]
+dep1 = { version =  \\"1.0.0\\" }
+dep2 = { version = \\"^0.6.0\\" }
+dep3 = { path = \\"/some/path/\\", version = \\"^0.33.6\\" }
+dep4 = { path = \\"/some/path/\\" }
+
+[tool.poetry.extras]
+extra_dep1 = \\"^0.8.3\\"
+extra_dep2 = \\"^0.9.4\\"
+extra_dep3 = \\"^0.4.0\\""
+`;
+
+exports[`manager/poetry/update updateDependency replaces nested value for path dependency 1`] = `
+"[tool.poetry]
+name = \\"example 2\\"
+version = \\"0.1.0\\"
+description = \\"\\"
+authors = [\\"John Doe <john.doe@gmail.com>\\"]
+
+[tool.poetry.dependencies]
+dep1 = { version =  \\"*\\" }
+dep2 = { version = \\"^0.6.0\\" }
+dep3 = { path = \\"/some/path/\\", version = \\"1.0.0\\" }
+dep4 = { path = \\"/some/path/\\" }
+
+[tool.poetry.extras]
+extra_dep1 = \\"^0.8.3\\"
+extra_dep2 = \\"^0.9.4\\"
+extra_dep3 = \\"^0.4.0\\""
+`;
+
+exports[`manager/poetry/update updateDependency upgrades dev-dependencies 1`] = `
+"[tool.poetry]
+name = \\"example 1\\"
+version = \\"0.1.0\\"
+description = \\"\\"
+authors = [\\"John Doe <john.doe@gmail.com>\\"]
+
+[tool.poetry.dependencies]
+dep1_ = \\"0.0.0\\"
+dep1 = \\"0.0.0\\"
+dep2 = \\"^0.6.0\\"
+dep3 = \\"^0.33.6\\"
+
+[tool.poetry.extras]
+extra_dep1 = \\"^0.8.3\\"
+extra_dep2 = \\"^0.9.4\\"
+extra_dep3 = \\"^0.4.0\\"
+
+[tool.poetry.dev-dependencies]
+dev_dep1 = \\"1.0.0\\"
+dev_dep2 = \\"Invalid version.\\""
+`;
+
+exports[`manager/poetry/update updateDependency upgrades extras 1`] = `
+"[tool.poetry]
+name = \\"example 1\\"
+version = \\"0.1.0\\"
+description = \\"\\"
+authors = [\\"John Doe <john.doe@gmail.com>\\"]
+
+[tool.poetry.dependencies]
+dep1_ = \\"0.0.0\\"
+dep1 = \\"0.0.0\\"
+dep2 = \\"^0.6.0\\"
+dep3 = \\"^0.33.6\\"
+
+[tool.poetry.extras]
+extra_dep1 = \\"1.0.0\\"
+extra_dep2 = \\"^0.9.4\\"
+extra_dep3 = \\"^0.4.0\\"
+
+[tool.poetry.dev-dependencies]
+dev_dep1 = \\"^3.0\\"
+dev_dep2 = \\"Invalid version.\\""
+`;
diff --git a/test/manager/poetry/_fixtures/pyproject.1.toml b/test/manager/poetry/_fixtures/pyproject.1.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d57ebe42d52be2c1235ca30f2bdf6c0493079196
--- /dev/null
+++ b/test/manager/poetry/_fixtures/pyproject.1.toml
@@ -0,0 +1,20 @@
+[tool.poetry]
+name = "example 1"
+version = "0.1.0"
+description = ""
+authors = ["John Doe <john.doe@gmail.com>"]
+
+[tool.poetry.dependencies]
+dep1_ = "0.0.0"
+dep1 = "0.0.0"
+dep2 = "^0.6.0"
+dep3 = "^0.33.6"
+
+[tool.poetry.extras]
+extra_dep1 = "^0.8.3"
+extra_dep2 = "^0.9.4"
+extra_dep3 = "^0.4.0"
+
+[tool.poetry.dev-dependencies]
+dev_dep1 = "^3.0"
+dev_dep2 = "Invalid version."
\ No newline at end of file
diff --git a/test/manager/poetry/_fixtures/pyproject.2.toml b/test/manager/poetry/_fixtures/pyproject.2.toml
new file mode 100644
index 0000000000000000000000000000000000000000..c12a5a77c1fe5362044030e4a9b96090bf80375c
--- /dev/null
+++ b/test/manager/poetry/_fixtures/pyproject.2.toml
@@ -0,0 +1,16 @@
+[tool.poetry]
+name = "example 2"
+version = "0.1.0"
+description = ""
+authors = ["John Doe <john.doe@gmail.com>"]
+
+[tool.poetry.dependencies]
+dep1 = { version =  "*" }
+dep2 = { version = "^0.6.0" }
+dep3 = { path = "/some/path/", version = "^0.33.6" }
+dep4 = { path = "/some/path/" }
+
+[tool.poetry.extras]
+extra_dep1 = "^0.8.3"
+extra_dep2 = "^0.9.4"
+extra_dep3 = "^0.4.0"
\ No newline at end of file
diff --git a/test/manager/poetry/_fixtures/pyproject.3.toml b/test/manager/poetry/_fixtures/pyproject.3.toml
new file mode 100644
index 0000000000000000000000000000000000000000..55bc5295e95ce5abc5ad854ef409773d5bc8b055
--- /dev/null
+++ b/test/manager/poetry/_fixtures/pyproject.3.toml
@@ -0,0 +1,5 @@
+[tool.poetry]
+name = "example 3"
+version = "0.1.0"
+description = ""
+authors = ["John Doe <john.doe@gmail.com>"]
diff --git a/test/manager/poetry/_fixtures/pyproject.4.toml b/test/manager/poetry/_fixtures/pyproject.4.toml
new file mode 100644
index 0000000000000000000000000000000000000000..0ae8e12d569f20cf37e8389aa9c2a219b75985e4
--- /dev/null
+++ b/test/manager/poetry/_fixtures/pyproject.4.toml
@@ -0,0 +1,11 @@
+[tool.poetry]
+name = "example 3"
+version = "0.1.0"
+description = ""
+authors = ["John Doe <john.doe@gmail.com>"]
+
+[tool.poetry.dependencies]
+foo = [
+    {version = "<=1.9", python = "^2.7"},
+    {version = "^2.0", python = "^3.4"}
+]
diff --git a/test/manager/poetry/artifacts.spec.js b/test/manager/poetry/artifacts.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a4d2f1c2038aed70709e521819eadb3c4bf8c42
--- /dev/null
+++ b/test/manager/poetry/artifacts.spec.js
@@ -0,0 +1,81 @@
+jest.mock('fs-extra');
+jest.mock('child-process-promise');
+
+const fs = require('fs-extra');
+const { exec } = require('child-process-promise');
+const poetry = require('../../../lib/manager/poetry/artifacts');
+
+const config = {
+  localDir: '/tmp/github/some/repo',
+};
+
+describe('.getArtifacts()', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+  });
+  it('returns null if no poetry.lock found', async () => {
+    const updatedDeps = [
+      {
+        depName: 'dep1',
+        currentValue: '1.2.3',
+      },
+    ];
+    expect(
+      await poetry.getArtifacts('pyproject.toml', updatedDeps, '', config)
+    ).toBeNull();
+  });
+  it('returns null if updatedDeps is empty', async () => {
+    expect(
+      await poetry.getArtifacts('pyproject.toml', [], '', config)
+    ).toBeNull();
+  });
+  it('returns null if unchanged', async () => {
+    platform.getFile.mockReturnValueOnce('Current poetry.lock');
+    exec.mockReturnValueOnce({
+      stdout: '',
+      stderror: '',
+    });
+    fs.readFile = jest.fn(() => 'Current poetry.lock');
+    const updatedDeps = [
+      {
+        depName: 'dep1',
+        currentValue: '1.2.3',
+      },
+    ];
+    expect(
+      await poetry.getArtifacts('pyproject.toml', updatedDeps, '', config)
+    ).toBeNull();
+  });
+  it('returns updated poetry.lock', async () => {
+    platform.getFile.mockReturnValueOnce('Old poetry.lock');
+    exec.mockReturnValueOnce({
+      stdout: '',
+      stderror: '',
+    });
+    fs.readFile = jest.fn(() => 'New poetry.lock');
+    const updatedDeps = [
+      {
+        depName: 'dep1',
+        currentValue: '1.2.3',
+      },
+    ];
+    expect(
+      await poetry.getArtifacts('pyproject.toml', updatedDeps, '{}', config)
+    ).not.toBeNull();
+  });
+  it('catches errors', async () => {
+    platform.getFile.mockReturnValueOnce('Current poetry.lock');
+    fs.outputFile = jest.fn(() => {
+      throw new Error('not found');
+    });
+    const updatedDeps = [
+      {
+        depName: 'dep1',
+        currentValue: '1.2.3',
+      },
+    ];
+    expect(
+      await poetry.getArtifacts('pyproject.toml', updatedDeps, '{}', config)
+    ).toMatchSnapshot();
+  });
+});
diff --git a/test/manager/poetry/extract.spec.js b/test/manager/poetry/extract.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a00b6d68d9fd15e73faea9e4e9d0b06a7381cf95
--- /dev/null
+++ b/test/manager/poetry/extract.spec.js
@@ -0,0 +1,89 @@
+const fs = require('fs');
+const { extractPackageFile } = require('../../../lib/manager/poetry/extract');
+
+const pyproject1toml = fs.readFileSync(
+  'test/manager/poetry/_fixtures/pyproject.1.toml',
+  'utf8'
+);
+
+const pyproject2toml = fs.readFileSync(
+  'test/manager/poetry/_fixtures/pyproject.2.toml',
+  'utf8'
+);
+
+const pyproject3toml = fs.readFileSync(
+  'test/manager/poetry/_fixtures/pyproject.3.toml',
+  'utf8'
+);
+
+const pyproject4toml = fs.readFileSync(
+  'test/manager/poetry/_fixtures/pyproject.4.toml',
+  'utf8'
+);
+
+describe('lib/manager/poetry/extract', () => {
+  describe('extractPackageFile()', () => {
+    let config;
+    beforeEach(() => {
+      config = {};
+    });
+    it('returns null for empty', () => {
+      expect(extractPackageFile('nothing here', config)).toBe(null);
+    });
+    it('extracts multiple dependencies', () => {
+      const res = extractPackageFile(pyproject1toml, config);
+      expect(res.deps).toMatchSnapshot();
+      expect(res.deps).toHaveLength(9);
+    });
+    it('extracts multiple dependencies (with dep = {version = "1.2.3"} case)', () => {
+      const res = extractPackageFile(pyproject2toml, config);
+      expect(res.deps).toMatchSnapshot();
+      expect(res.deps).toHaveLength(7);
+    });
+    it('handles case with no dependencies', () => {
+      const res = extractPackageFile(pyproject3toml, config);
+      expect(res).toBeNull();
+    });
+    it('handles multiple constraint dependencies', () => {
+      const res = extractPackageFile(pyproject4toml, config);
+      expect(res.deps).toMatchSnapshot();
+      expect(res.deps).toHaveLength(1);
+    });
+    it('skips git dependencies', () => {
+      const content =
+        '[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git"}\r\nwerkzeug = ">=0.14"';
+      const res = extractPackageFile(content, config).deps;
+      expect(res[0].depName).toBe('flask');
+      expect(res[0].currentValue).toBe('');
+      expect(res[0].skipReason).toBe('git-dependency');
+      expect(res).toHaveLength(2);
+    });
+    it('skips git dependencies', () => {
+      const content =
+        '[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git", version="1.2.3"}\r\nwerkzeug = ">=0.14"';
+      const res = extractPackageFile(content, config).deps;
+      expect(res[0].depName).toBe('flask');
+      expect(res[0].currentValue).toBe('1.2.3');
+      expect(res[0].skipReason).toBe('git-dependency');
+      expect(res).toHaveLength(2);
+    });
+    it('skips path dependencies', () => {
+      const content =
+        '[tool.poetry.dependencies]\r\nflask = {path = "/some/path/"}\r\nwerkzeug = ">=0.14"';
+      const res = extractPackageFile(content, config).deps;
+      expect(res[0].depName).toBe('flask');
+      expect(res[0].currentValue).toBe('');
+      expect(res[0].skipReason).toBe('path-dependency');
+      expect(res).toHaveLength(2);
+    });
+    it('skips path dependencies', () => {
+      const content =
+        '[tool.poetry.dependencies]\r\nflask = {path = "/some/path/", version = "1.2.3"}\r\nwerkzeug = ">=0.14"';
+      const res = extractPackageFile(content, config).deps;
+      expect(res[0].depName).toBe('flask');
+      expect(res[0].currentValue).toBe('1.2.3');
+      expect(res[0].skipReason).toBe('path-dependency');
+      expect(res).toHaveLength(2);
+    });
+  });
+});
diff --git a/test/manager/poetry/update.spec.js b/test/manager/poetry/update.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..d02b76de98c95d0d3d3c23d4656437191a288f56
--- /dev/null
+++ b/test/manager/poetry/update.spec.js
@@ -0,0 +1,134 @@
+const fs = require('fs');
+const { updateDependency } = require('../../../lib/manager/poetry/update');
+
+const pyproject1toml = fs.readFileSync(
+  'test/manager/poetry/_fixtures/pyproject.1.toml',
+  'utf8'
+);
+
+const pyproject2toml = fs.readFileSync(
+  'test/manager/poetry/_fixtures/pyproject.2.toml',
+  'utf8'
+);
+
+describe('manager/poetry/update', () => {
+  describe('updateDependency', () => {
+    it('replaces existing value', () => {
+      const upgrade = {
+        depName: 'dep1',
+        depType: 'dependencies',
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject1toml, upgrade);
+      expect(res).not.toEqual(pyproject1toml);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+      expect(res).toMatchSnapshot();
+    });
+    it('handles already replace values', () => {
+      const upgrade = {
+        depName: 'dep1',
+        depType: 'dependencies',
+        newValue: '0.0.0',
+      };
+      const res = updateDependency(pyproject1toml, upgrade);
+      expect(res).toEqual(pyproject1toml);
+    });
+    it('replaces nested value', () => {
+      const upgrade = {
+        depName: 'dep1',
+        depType: 'dependencies',
+        newValue: '1.0.0',
+        nestedVersion: true,
+      };
+      const res = updateDependency(pyproject2toml, upgrade);
+      expect(res).not.toEqual(pyproject2toml);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+      expect(res).toMatchSnapshot();
+    });
+    it('replaces nested value for path dependency', () => {
+      const upgrade = {
+        depName: 'dep3',
+        depType: 'dependencies',
+        newValue: '1.0.0',
+        nestedVersion: true,
+      };
+      const res = updateDependency(pyproject2toml, upgrade);
+      expect(res).not.toEqual(pyproject2toml);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+      expect(res).toMatchSnapshot();
+    });
+    it('gracefully handles nested value for path dependency withou version field', () => {
+      const upgrade = {
+        depName: 'dep4',
+        depType: 'dependencies',
+        newValue: '1.0.0',
+        nestedVersion: true,
+      };
+      const res = updateDependency(pyproject2toml, upgrade);
+      expect(res).toBe(null);
+    });
+    it('upgrades extras', () => {
+      const upgrade = {
+        depName: 'extra_dep1',
+        depType: 'extras',
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject1toml, upgrade);
+      expect(res).not.toEqual(pyproject1toml);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+      expect(res).toMatchSnapshot();
+    });
+    it('upgrades dev-dependencies', () => {
+      const upgrade = {
+        depName: 'dev_dep1',
+        depType: 'dev-dependencies',
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject1toml, upgrade);
+      expect(res).not.toEqual(pyproject1toml);
+      expect(res.includes(upgrade.newValue)).toBe(true);
+      expect(res).toMatchSnapshot();
+    });
+    it('returns null if upgrade is null', () => {
+      const res = updateDependency(null, null);
+      expect(res).toBe(null);
+    });
+    it('handles nonexistent depType gracefully', () => {
+      const upgrade = {
+        depName: 'dev1',
+        depType: '!invalid-dev-type!',
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject1toml, upgrade);
+      expect(res).toBe(null);
+    });
+    it('handles nonexistent depType gracefully', () => {
+      const upgrade = {
+        depName: 'dev_dev1',
+        depType: 'dev-dependencies',
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject2toml, upgrade);
+      expect(res).toBe(null);
+    });
+    it('handles nonexistent depName gracefully', () => {
+      const upgrade = {
+        depName: '~invalid-dep-name~',
+        depType: 'dependencies',
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject1toml, upgrade);
+      expect(res).toBe(null);
+    });
+    it('handles nonexistent depName with nested value gracefully', () => {
+      const upgrade = {
+        depName: '~invalid-dep-name~',
+        depType: 'dependencies',
+        nestedVersion: true,
+        newValue: '1.0.0',
+      };
+      const res = updateDependency(pyproject2toml, upgrade);
+      expect(res).toBe(null);
+    });
+  });
+});
diff --git a/test/versioning/poetry.spec.js b/test/versioning/poetry.spec.js
index 053aea5df7fb0e1fd8869e05b7e22940088fc06c..7938f549180c3e23c4506987acffd82cac752dc8 100644
--- a/test/versioning/poetry.spec.js
+++ b/test/versioning/poetry.spec.js
@@ -114,6 +114,9 @@ describe('semver.getNewValue()', () => {
     expect(semver.getNewValue('   1.0.0', 'bump', '1.0.0', '1.1.0')).toEqual(
       '1.1.0'
     );
+    expect(semver.getNewValue('1.0.0', 'bump', '1.0.0', '1.1.0')).toEqual(
+      '1.1.0'
+    );
   });
   it('bumps equals', () => {
     expect(semver.getNewValue('=1.0.0', 'bump', '1.0.0', '1.1.0')).toEqual(
@@ -137,17 +140,12 @@ describe('semver.getNewValue()', () => {
       '=1.1.0'
     );
   });
-  it('bumps version range', () => {
-    expect(semver.getNewValue('1.0.0', 'bump', '1.0.0', '1.1.0')).toEqual(
-      '1.1.0'
-    );
-  });
   it('bumps short caret to same', () => {
     expect(semver.getNewValue('^1.0', 'bump', '1.0.0', '1.0.7')).toEqual(
       '^1.0'
     );
   });
-  it('replaces with newer', () => {
+  it('replaces caret with newer', () => {
     expect(semver.getNewValue('^1.0.0', 'replace', '1.0.0', '2.0.7')).toEqual(
       '^2.0.0'
     );
@@ -162,7 +160,7 @@ describe('semver.getNewValue()', () => {
       '^2.0.7'
     );
   });
-  it('updates naked caret', () => {
+  it('bumps naked caret', () => {
     expect(semver.getNewValue('^1', 'bump', '1.0.0', '2.1.7')).toEqual('^2');
   });
   it('bumps naked tilde', () => {
@@ -239,4 +237,16 @@ describe('semver.getNewValue()', () => {
       semver.getNewValue('<=   1.3.4', 'replace', '1.2.3', '1.5.0')
     ).toEqual('<= 1.5.0');
   });
+  it('handles replacing short caret versions', () => {
+    expect(semver.getNewValue('^1.2', 'replace', '1.2.3', '2.0.0')).toEqual(
+      '^2.0'
+    );
+    expect(semver.getNewValue('^1', 'replace', '1.2.3', '2.0.0')).toEqual('^2');
+  });
+  it('handles replacing short tilde versions', () => {
+    expect(semver.getNewValue('~1.2', 'replace', '1.2.3', '2.0.0')).toEqual(
+      '~2.0'
+    );
+    expect(semver.getNewValue('~1', 'replace', '1.2.3', '2.0.0')).toEqual('~2');
+  });
 });
diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap
index 9ca0e9b27b8372d0ab02edda8839611b2d433a88..ff92c6208d5f6cbb276762a1a613a3a7af39ac91 100644
--- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap
+++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap
@@ -71,6 +71,9 @@ Object {
   "pipenv": Array [
     Object {},
   ],
+  "poetry": Array [
+    Object {},
+  ],
   "terraform": Array [
     Object {},
   ],
diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md
index 96668fcb6bbb49358a2a550a75733f1d03e3120e..af42f9c4937eaecc1d1a88be28874a5cead6c483 100644
--- a/website/docs/configuration-options.md
+++ b/website/docs/configuration-options.md
@@ -703,6 +703,8 @@ Add configuration here to change pipenv settings, e.g. to change the file patter
 
 Warning: 'pipenv' support is currently in beta, so it is not enabled by default. You will need to configure `{ "pipenv": { "enabled": true }}" to enable.
 
+## poetry
+
 ## postUpdateOptions
 
 `gomodTidy`: Run `go mod tidy` after Go module updates