Commit c6a2d6e1 by Sébastien Eustace Committed by GitHub

Improve the add and init commands (#1221)

* Add the ability to specify path and git dependencies directly in add

* Add the ability to add git and path dependencies via the init command

* Automatically select latest prereleases if only prereleases are available

* Update documentation for the add command

* Add the ability to add complete dependencies in one go
parent 6f4aa21c
......@@ -15,6 +15,9 @@
- Added the ability to specify packages on a per-format basis.
- Added support for custom urls in metadata.
- Full environment markers are now supported for dependencies via the `markers` property.
- Added the ability to specify git dependencies directly in `add`, it no longer requires the `--git` option.
- Added the ability to specify path dependencies directly in `add`, it no longer requires the `--path` option.
- Added the ability to add git and path dependencies via the `init` command.
### Changed
......@@ -24,6 +27,9 @@
- The `debug:resolve` command has been renamed to `debug resolve`.
- The `self:update` command has been renamed to `self update`.
- Changed the way virtualenvs are stored (names now depend on the project's path).
- The `--git` option of the `add` command has been removed.
- The `--path` option of the `add` command has been removed.
- The `add` command will now automatically select the latest prerelease if only prereleases are available.
### Fixed
......
......@@ -182,18 +182,42 @@ poetry will choose a suitable one based on the available package versions.
poetry add requests pendulum
```
You also can specify a constraint when adding a package, like so:
```bash
poetry add pendulum@^2.0.5
poetry add "pendulum>=2.0.5"
```
If you try to add a package that is already present, you will get an error.
However, if you specify a constraint, like above, the dependency will be updated
by using the specified constraint. If you want to get the latest version of an already
present dependency you can use the special `latest` constraint:
```bash
poetry add pendulum@latest
```
You can also add `git` dependencies:
```bash
poetry add pendulum --git https://github.com/sdispater/pendulum.git
poetry add git+https://github.com/sdispater/pendulum.git
```
If you need to checkout a specific branch, tag or revision,
you can specify it when using `add`:
```bash
poetry add git+https://github.com/sdispater/pendulum.git@develop
poetry add git+https://github.com/sdispater/pendulum.git@2.0.5
```
or make them point to a local directory or file:
```bash
poetry add my-package --path ../my-package/
poetry add my-package --path ../my-package/dist/my-package-0.1.0.tar.gz
poetry add my-package --path ../my-package/dist/my_package-0.1.0.whl
poetry add ./my-package/
poetry add ../my-package/dist/my-package-0.1.0.tar.gz
poetry add ../my-package/dist/my_package-0.1.0.whl
```
Path dependencies pointing to a local directory will be installed in editable mode (i.e. setuptools "develop mode").
......@@ -201,17 +225,24 @@ It means that changes in the local directory will be reflected directly in envir
If you don't want the dependency to be installed in editable mode you can specify it in the `pyproject.toml` file:
```
```toml
[tool.poetry.dependencies]
my-package = {path = "../my/path", develop = false}
```
If the package(s) you want to install provide extras, you can specify them
when adding the package:
```bash
poetry add requests[security,socks]
poetry add "requests[security,socks]~=2.22.0"
poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]"
```
### Options
* `--dev (-D)`: Add package as development dependency.
* `--git`: The url of the Git repository.
* `--path`: The path to a dependency.
* `--extras (-E)`: Extras to activate for the dependency.
* `--optional` : Add as an optional dependency.
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables --verbose).
......
......@@ -13,8 +13,6 @@ class AddCommand(EnvCommand, InitCommand):
arguments = [argument("name", "Packages to add.", multiple=True)]
options = [
option("dev", "D", "Add package as development dependency."),
option("git", None, "The url of the Git repository.", flag=False),
option("path", None, "The path to a dependency.", flag=False),
option(
"extras",
"E",
......@@ -58,17 +56,11 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
packages = self.argument("name")
is_dev = self.option("dev")
if (self.option("git") or self.option("path") or self.option("extras")) and len(
packages
) > 1:
if self.option("extras") and len(packages) > 1:
raise ValueError(
"You can only specify one package "
"when using the --git or --path options"
"You can only specify one package " "when using the --extras option"
)
if self.option("git") and self.option("path"):
raise RuntimeError("--git and --path cannot be used at the same time")
section = "dependencies"
if is_dev:
section = "dev-dependencies"
......@@ -83,32 +75,27 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
for name in packages:
for key in poetry_content[section]:
if key.lower() == name.lower():
pair = self._parse_requirements([name])[0]
if "git" in pair or pair.get("version") == "latest":
continue
raise ValueError("Package {} is already present".format(name))
if self.option("git") or self.option("path"):
requirements = {packages[0]: ""}
else:
requirements = self._determine_requirements(
packages, allow_prereleases=self.option("allow-prereleases")
)
requirements = self._format_requirements(requirements)
requirements = self._determine_requirements(
packages, allow_prereleases=self.option("allow-prereleases")
)
# validate requirements format
for constraint in requirements.values():
parse_constraint(constraint)
for _constraint in requirements:
if "version" in _constraint:
# Validate version constraint
parse_constraint(_constraint["version"])
for name, _constraint in requirements.items():
constraint = inline_table()
constraint["version"] = _constraint
if self.option("git"):
del constraint["version"]
constraint["git"] = self.option("git")
elif self.option("path"):
del constraint["version"]
for name, value in _constraint.items():
if name == "name":
continue
constraint["path"] = self.option("path")
constraint[name] = value
if self.option("optional"):
constraint["optional"] = True
......@@ -135,7 +122,7 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
if len(constraint) == 1 and "version" in constraint:
constraint = constraint["version"]
poetry_content[section][name] = constraint
poetry_content[section][_constraint["name"]] = constraint
# Write new content
self.poetry.file.write(content)
......@@ -152,7 +139,7 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
installer.dry_run(self.option("dry-run"))
installer.update(True)
installer.whitelist(requirements)
installer.whitelist([r["name"] for r in requirements])
try:
status = installer.run()
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import re
from typing import Dict
from typing import List
from typing import Tuple
from typing import Union
from cleo import option
from tomlkit import inline_table
from poetry.utils._compat import Path
from poetry.utils._compat import OrderedDict
from .command import Command
from .env_command import EnvCommand
......@@ -133,10 +140,20 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
requirements = {}
question = (
"Would you like to define your dependencies" " (require) interactively?"
question = "Would you like to define your main dependencies interactively?"
help_message = (
"You can specify a package in the following forms:\n"
" - A single name (<b>requests</b>)\n"
" - A name and a constraint (<b>requests ^2.23.0</b>)\n"
" - A git url (<b>https://github.com/sdispater/poetry.git</b>)\n"
" - A git url with a revision (<b>https://github.com/sdispater/poetry.git@develop</b>)\n"
" - A file path (<b>../my-package/my-package.whl</b>)\n"
" - A directory (<b>../my-package/</b>)\n"
)
help_displayed = False
if self.confirm(question, True):
self.line(help_message)
help_displayed = True
requirements = self._format_requirements(
self._determine_requirements(self.option("dependency"))
)
......@@ -149,6 +166,9 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
" (require-dev) interactively"
)
if self.confirm(question, True):
if not help_displayed:
self.line(help_message)
dev_requirements = self._format_requirements(
self._determine_requirements(self.option("dev-dependency"))
)
......@@ -182,13 +202,24 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
def _determine_requirements(
self, requires, allow_prereleases=False
): # type: (List[str], bool) -> List[str]
): # type: (List[str], bool) -> List[Dict[str, str]]
if not requires:
requires = []
package = self.ask("Search for package:")
package = self.ask("Add a package:")
while package is not None:
matches = self._get_pool().search(package)
constraint = self._parse_requirements([package])[0]
if (
"git" in constraint
or "path" in constraint
or "version" in constraint
):
self.line("Adding <info>{}</info>".format(package))
requires.append(constraint)
package = self.ask("\nAdd a package:")
continue
matches = self._get_pool().search(constraint["name"])
if not matches:
self.line("<error>Unable to find package</error>")
......@@ -212,7 +243,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
)
# no constraint yet, determine the best version automatically
if package is not False and " " not in package:
if package is not False and "version" not in constraint:
question = self.create_question(
"Enter the version constraint to require "
"(or leave blank to use the latest version):"
......@@ -220,30 +251,35 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
question.attempts = 3
question.validator = lambda x: (x or "").strip() or False
constraint = self.ask(question)
package_constraint = self.ask(question)
if constraint is None:
_, constraint = self._find_best_version_for_package(package)
if package_constraint is None:
_, package_constraint = self._find_best_version_for_package(
package
)
self.line(
"Using version <info>{}</info> for <info>{}</info>".format(
constraint, package
package_constraint, package
)
)
package += " {}".format(constraint)
constraint["version"] = package_constraint
if package is not False:
requires.append(package)
requires.append(constraint)
package = self.ask("\nSearch for a package:")
package = self.ask("\nAdd a package:")
return requires
requires = self._parse_name_version_pairs(requires)
requires = self._parse_requirements(requires)
result = []
for requirement in requires:
if "version" not in requirement:
if "git" in requirement or "path" in requirement:
result.append(requirement)
continue
elif "version" not in requirement:
# determine the best version automatically
name, version = self._find_best_version_for_package(
requirement["name"], allow_prereleases=allow_prereleases
......@@ -265,7 +301,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
requirement["name"] = name
result.append("{} {}".format(requirement["name"], requirement["version"]))
result.append(requirement)
return result
......@@ -285,28 +321,123 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
"Could not find a matching version of package {}".format(name)
)
return (package.pretty_name, selector.find_recommended_require_version(package))
return package.pretty_name, selector.find_recommended_require_version(package)
def _parse_requirements(
self, requirements
): # type: (List[str]) -> List[Dict[str, str]]
from poetry.puzzle.provider import Provider
def _parse_name_version_pairs(self, pairs): # type: (list) -> list
result = []
for i in range(len(pairs)):
pair = re.sub("^([^=: ]+)[=: ](.*)$", "\\1 \\2", pairs[i].strip())
try:
cwd = self.poetry.file.parent
except RuntimeError:
cwd = Path.cwd()
for requirement in requirements:
requirement = requirement.strip()
extras = []
extras_m = re.search(r"\[([\w\d,-_]+)\]$", requirement)
if extras_m:
extras = [e.strip() for e in extras_m.group(1).split(",")]
requirement, _ = requirement.split("[")
if requirement.startswith(("git+https://", "git+ssh://")):
url = requirement.lstrip("git+")
rev = None
if "@" in url:
url, rev = url.split("@")
pair = OrderedDict(
[("name", url.split("/")[-1].rstrip(".git")), ("git", url)]
)
if rev:
pair["rev"] = rev
if extras:
pair["extras"] = extras
package = Provider.get_package_from_vcs(
"git", url, reference=pair.get("rev")
)
pair["name"] = package.name
result.append(pair)
continue
elif (os.path.sep in requirement or "/" in requirement) and cwd.joinpath(
requirement
).exists():
path = cwd.joinpath(requirement)
if path.is_file():
package = Provider.get_package_from_file(path.resolve())
else:
package = Provider.get_package_from_directory(path)
result.append(
OrderedDict(
[
("name", package.name),
("path", path.relative_to(cwd).as_posix()),
]
+ ([("extras", extras)] if extras else [])
)
)
continue
pair = re.sub(
"^([^@=: ]+)(?:@|==|(?<![<>~!])=|:| )(.*)$", "\\1 \\2", requirement
)
pair = pair.strip()
require = OrderedDict()
if " " in pair:
name, version = pair.split(" ", 2)
result.append({"name": name, "version": version})
require["name"] = name
require["version"] = version
else:
result.append({"name": pair})
m = re.match(
"^([^><=!: ]+)((?:>=|<=|>|<|!=|~=|~|\^).*)$", requirement.strip()
)
if m:
name, constraint = m.group(1), m.group(2)
extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
if extras_m:
extras = [e.strip() for e in extras_m.group(1).split(",")]
name, _ = name.split("[")
require["name"] = name
require["version"] = constraint
else:
extras_m = re.search(r"\[([\w\d,-_]+)\]$", pair)
if extras_m:
extras = [e.strip() for e in extras_m.group(1).split(",")]
pair, _ = pair.split("[")
require["name"] = pair
if extras:
require["extras"] = extras
result.append(require)
return result
def _format_requirements(self, requirements): # type: (List[str]) -> dict
def _format_requirements(
self, requirements
): # type: (List[Dict[str, str]]) -> Dict[str, Union[str, Dict[str, str]]]
requires = {}
requirements = self._parse_name_version_pairs(requirements)
for requirement in requirements:
requires[requirement["name"]] = requirement["version"]
name = requirement.pop("name")
if "version" in requirement and len(requirement) == 1:
constraint = requirement["version"]
else:
constraint = inline_table()
constraint.trivia.trail = "\n"
constraint.update(requirement)
requires[name] = constraint
return requires
......
......@@ -9,6 +9,7 @@ from clikit.ui.components import ProgressIndicator
from contextlib import contextmanager
from tempfile import mkdtemp
from typing import List
from typing import Optional
from poetry.packages import Dependency
from poetry.packages import DependencyPackage
......@@ -34,6 +35,7 @@ from poetry.utils.helpers import safe_rmtree
from poetry.utils.env import EnvManager
from poetry.utils.env import EnvCommandError
from poetry.utils.setup_reader import SetupReader
from poetry.utils.toml_file import TomlFile
from poetry.version.markers import MarkerUnion
from poetry.vcs.git import Git
......@@ -56,10 +58,7 @@ class Provider:
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"}
def __init__(
self,
package, # type: Package
pool, # type: Pool
io,
self, package, pool, io # type: Package # type: Pool
): # type: (...) -> None
self._package = package
self._pool = pool
......@@ -158,59 +157,92 @@ class Provider:
Basically, we clone the repository in a temporary directory
and get the information we need by checking out the specified reference.
"""
if dependency.vcs != "git":
raise ValueError("Unsupported VCS dependency {}".format(dependency.vcs))
package = self.get_package_from_vcs(
dependency.vcs,
dependency.source,
dependency.reference,
name=dependency.name,
)
tmp_dir = Path(mkdtemp(prefix="pypoetry-git-{}".format(dependency.name)))
if dependency.tag or dependency.rev:
package.source_reference = dependency.reference
for extra in dependency.extras:
if extra in package.extras:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
return [package]
@classmethod
def get_package_from_vcs(
cls, vcs, url, reference=None, name=None
): # type: (str, str, Optional[str], Optional[str]) -> Package
if vcs != "git":
raise ValueError("Unsupported VCS dependency {}".format(vcs))
tmp_dir = Path(
mkdtemp(prefix="pypoetry-git-{}".format(url.split("/")[-1].rstrip(".git")))
)
try:
git = Git()
git.clone(dependency.source, tmp_dir)
git.checkout(dependency.reference, tmp_dir)
revision = git.rev_parse(dependency.reference, tmp_dir).strip()
if dependency.tag or dependency.rev:
revision = dependency.reference
git.clone(url, tmp_dir)
if reference is not None:
git.checkout(reference, tmp_dir)
else:
reference = "HEAD"
directory_dependency = DirectoryDependency(
dependency.name,
tmp_dir,
category=dependency.category,
optional=dependency.is_optional(),
)
for extra in dependency.extras:
directory_dependency.extras.append(extra)
revision = git.rev_parse(reference, tmp_dir).strip()
package = self.search_for_directory(directory_dependency)[0]
package = cls.get_package_from_directory(tmp_dir, name=name)
package.source_type = "git"
package.source_url = dependency.source
package.source_url = url
package.source_reference = revision
except Exception:
raise
finally:
safe_rmtree(str(tmp_dir))
return [package]
return package
def search_for_file(self, dependency): # type: (FileDependency) -> List[Package]
if dependency.path.suffix == ".whl":
meta = pkginfo.Wheel(str(dependency.full_path))
else:
# Assume sdist
meta = pkginfo.SDist(str(dependency.full_path))
package = self.get_package_from_file(dependency.full_path)
if dependency.name != meta.name:
if dependency.name != package.name:
# For now, the dependency's name must match the actual package's name
raise RuntimeError(
"The dependency name for {} does not match the actual package's name: {}".format(
dependency.name, meta.name
dependency.name, package.name
)
)
package.source_url = dependency.path.as_posix()
package.hashes = [dependency.hash()]
for extra in dependency.extras:
if extra in package.extras:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
return [package]
@classmethod
def get_package_from_file(cls, file_path): # type: (Path) -> Package
if file_path.suffix == ".whl":
meta = pkginfo.Wheel(str(file_path))
else:
# Assume sdist
meta = pkginfo.SDist(str(file_path))
package = Package(meta.name, meta.version)
package.source_type = "file"
package.source_url = dependency.path.as_posix()
package.source_url = file_path.as_posix()
package.description = meta.summary
for req in meta.requires_dist:
......@@ -227,7 +259,16 @@ class Provider:
if meta.requires_python:
package.python_versions = meta.requires_python
package.hashes = [dependency.hash()]
return package
def search_for_directory(
self, dependency
): # type: (DirectoryDependency) -> List[Package]
package = self.get_package_from_directory(
dependency.full_path, name=dependency.name
)
package.source_url = dependency.path.as_posix()
for extra in dependency.extras:
if extra in package.extras:
......@@ -238,13 +279,23 @@ class Provider:
return [package]
def search_for_directory(
self, dependency
): # type: (DirectoryDependency) -> List[Package]
if dependency.supports_poetry():
@classmethod
def get_package_from_directory(
cls, directory, name=None
): # type: (Path, Optional[str]) -> Package
supports_poetry = False
pyproject = directory.joinpath("pyproject.toml")
if pyproject.exists():
pyproject = TomlFile(pyproject)
pyproject_content = pyproject.read()
supports_poetry = (
"tool" in pyproject_content and "poetry" in pyproject_content["tool"]
)
if supports_poetry:
from poetry.poetry import Poetry
poetry = Poetry.create(dependency.full_path)
poetry = Poetry.create(directory)
pkg = poetry.package
package = Package(pkg.name, pkg.version)
......@@ -264,25 +315,25 @@ class Provider:
else:
# Execute egg_info
current_dir = os.getcwd()
os.chdir(str(dependency.full_path))
os.chdir(str(directory))
try:
cwd = dependency.full_path
cwd = directory
venv = EnvManager().get(cwd)
venv.run("python", "setup.py", "egg_info")
except EnvCommandError:
result = SetupReader.read_from_directory(dependency.full_path)
result = SetupReader.read_from_directory(directory)
if not result["name"]:
# The name could not be determined
# We use the dependency name
result["name"] = dependency.name
result["name"] = name
if not result["version"]:
# The version could not be determined
# so we raise an error since it is mandatory
raise RuntimeError(
"Unable to retrieve the package version for {}".format(
dependency.path
directory
)
)
......@@ -321,12 +372,12 @@ class Provider:
egg_info = next(
Path(p)
for p in glob.glob(
os.path.join(str(dependency.full_path), "**", "*.egg-info"),
os.path.join(str(directory), "**", "*.egg-info"),
recursive=True,
)
)
else:
egg_info = next(dependency.full_path.glob("**/*.egg-info"))
egg_info = next(directory.glob("**/*.egg-info"))
meta = pkginfo.UnpackedSDist(str(egg_info))
package_name = meta.name
......@@ -345,16 +396,16 @@ class Provider:
finally:
os.chdir(current_dir)
package = Package(package_name, package_version)
if dependency.name != package.name:
if name and name != package_name:
# For now, the dependency's name must match the actual package's name
raise RuntimeError(
"The dependency name for {} does not match the actual package's name: {}".format(
dependency.name, package.name
name, package_name
)
)
package = Package(package_name, package_version)
package.description = package_summary
for req in reqs:
......@@ -373,16 +424,9 @@ class Provider:
package.python_versions = python_requires
package.source_type = "directory"
package.source_url = dependency.path.as_posix()
package.source_url = directory.as_posix()
for extra in dependency.extras:
if extra in package.extras:
for dep in package.extras[extra]:
dep.activate()
package.requires += package.extras[extra]
return [package]
return package
def incompatibilities_for(
self, package
......
......@@ -26,8 +26,9 @@ class VersionSelector(object):
constraint = parse_constraint("*")
candidates = self._pool.find_packages(
package_name, constraint, allow_prereleases=allow_prereleases
package_name, constraint, allow_prereleases=True
)
only_prereleases = all([c.version.is_prerelease() for c in candidates])
if not candidates:
return False
......@@ -37,7 +38,12 @@ class VersionSelector(object):
# Select highest version if we have many
package = candidates[0]
for candidate in candidates:
if candidate.is_prerelease() and not dependency.allows_prereleases():
if (
candidate.is_prerelease()
and not dependency.allows_prereleases()
and not allow_prereleases
and not only_prereleases
):
continue
# Select highest version of the two
......@@ -52,24 +58,20 @@ class VersionSelector(object):
return self._transform_version(version.text, package.pretty_version)
def _transform_version(self, version, pretty_version):
# attempt to transform 2.1.1 to 2.1
# this allows you to upgrade through minor versions
try:
parsed = Version.parse(version)
parts = [parsed.major, parsed.minor, parsed.patch]
except ValueError:
return pretty_version
# check to see if we have a semver-looking version
if len(parts) == 3:
# remove the last parts (the patch version number and any extra)
if parts[0] != 0:
del parts[2]
parts = parts[: parsed.precision]
# check to see if we have a semver-looking version
if len(parts) < 3:
version = pretty_version
else:
version = ".".join(str(p) for p in parts)
if parsed.is_prerelease():
version += "-{}".format(".".join(str(p) for p in parsed.prerelease))
else:
return pretty_version
return "^{}".format(version)
import sys
import pytest
from cleo.testers import CommandTester
from poetry.utils._compat import Path
from tests.helpers import get_dependency
from tests.helpers import get_package
......@@ -39,14 +42,14 @@ Package operations: 1 install, 0 updates, 0 removals
assert content["dependencies"]["cachy"] == "^0.2.0"
def test_add_constraint(app, repo, installer):
def test_add_equal_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy=0.1.0")
tester.execute("cachy==0.1.0")
expected = """\
......@@ -66,6 +69,67 @@ Package operations: 1 install, 0 updates, 0 removals
assert len(installer.installs) == 1
def test_add_greater_constraint(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("cachy", "0.1.0"))
repo.add_package(get_package("cachy", "0.2.0"))
tester.execute("cachy>=0.1.0")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing cachy (0.2.0)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 1
def test_add_constraint_with_extras(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
cachy1 = get_package("cachy", "0.1.0")
cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]}
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
cachy1.requires = [msgpack_dep]
repo.add_package(get_package("cachy", "0.2.0"))
repo.add_package(cachy1)
repo.add_package(get_package("msgpack-python", "0.5.3"))
tester.execute("cachy[msgpack]^0.1.0")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing msgpack-python (0.5.3)
- Installing cachy (0.1.0)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 2
def test_add_constraint_dependencies(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
......@@ -106,7 +170,7 @@ def test_add_git_constraint(app, repo, installer):
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("demo --git https://github.com/demo/demo.git")
tester.execute("git+https://github.com/demo/demo.git")
expected = """\
......@@ -140,7 +204,7 @@ def test_add_git_constraint_with_poetry(app, repo, installer):
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("demo --git https://github.com/demo/pyproject-demo.git")
tester.execute("git+https://github.com/demo/pyproject-demo.git")
expected = """\
......@@ -161,13 +225,121 @@ Package operations: 2 installs, 0 updates, 0 removals
assert len(installer.installs) == 2
def test_add_file_constraint_wheel(app, repo, installer):
def test_add_git_constraint_with_extras(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
repo.add_package(get_package("tomlkit", "0.5.5"))
tester.execute("git+https://github.com/demo/demo.git[foo,bar]")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 4 installs, 0 updates, 0 removals
- Installing cleo (0.6.5)
- Installing pendulum (1.4.4)
- Installing tomlkit (0.5.5)
- Installing demo (0.1.2 9cf87a2)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 4
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {
"git": "https://github.com/demo/demo.git",
"extras": ["foo", "bar"],
}
def test_add_directory_constraint(app, repo, installer, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cleo", "0.6.5"))
tester.execute("../git/github.com/demo/demo")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 ../git/github.com/demo/demo)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 2
content = app.poetry.file.read()["tool"]["poetry"]
assert "demo" in content["dependencies"]
assert content["dependencies"]["demo"] == {"path": "../git/github.com/demo/demo"}
def test_add_directory_with_poetry(app, repo, installer, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("../git/github.com/demo/pyproject-demo")
expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing pendulum (1.4.4)
- Installing demo (0.1.2 ../git/github.com/demo/pyproject-demo)
"""
assert expected == tester.io.fetch_output()
assert len(installer.installs) == 2
def test_add_file_constraint_wheel(app, repo, installer, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("demo --path ../distributions/demo-0.1.0-py2.py3-none-any.whl")
tester.execute("../distributions/demo-0.1.0-py2.py3-none-any.whl")
expected = """\
......@@ -195,13 +367,16 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_file_constraint_sdist(app, repo, installer):
def test_add_file_constraint_sdist(app, repo, installer, mocker):
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__) / ".."
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("pendulum", "1.4.4"))
tester.execute("demo --path ../distributions/demo-0.1.0.tar.gz")
tester.execute("../distributions/demo-0.1.0.tar.gz")
expected = """\
......@@ -229,7 +404,7 @@ Package operations: 2 installs, 0 updates, 0 removals
}
def test_add_constraint_with_extras(app, repo, installer):
def test_add_constraint_with_extras_option(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
......@@ -407,3 +582,72 @@ Package operations: 1 install, 0 updates, 0 removals
assert "pyyaml" in content["dependencies"]
assert content["dependencies"]["pyyaml"] == "^3.13"
def test_add_should_display_an_error_when_adding_existing_package_with_no_constraint(
app, repo, installer
):
content = app.poetry.file.read()
content["tool"]["poetry"]["dependencies"]["foo"] = "^1.0"
app.poetry.file.write(content)
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("foo", "1.1.2"))
with pytest.raises(ValueError) as e:
tester.execute("foo")
assert "Package foo is already present" == str(e.value)
def test_add_chooses_prerelease_if_only_prereleases_are_available(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("foo", "1.2.3b0"))
repo.add_package(get_package("foo", "1.2.3b1"))
tester.execute("foo")
expected = """\
Using version ^1.2.3-beta.1 for foo
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing foo (1.2.3b1)
"""
assert expected in tester.io.fetch_output()
def test_add_preferes_stable_releases(app, repo, installer):
command = app.find("add")
tester = CommandTester(command)
repo.add_package(get_package("foo", "1.2.3"))
repo.add_package(get_package("foo", "1.2.4b1"))
tester.execute("foo")
expected = """\
Using version ^1.2.3 for foo
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 1 install, 0 updates, 0 removals
- Installing foo (1.2.3)
"""
assert expected in tester.io.fetch_output()
......@@ -88,10 +88,10 @@ license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
pendulum = "^2.0"
pendulum = "^2.0.0"
[tool.poetry.dev-dependencies]
pytest = "^3.6"
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
......@@ -135,3 +135,305 @@ python = "^{python}"
)
assert expected in tester.io.fetch_output()
def test_interactive_with_git_dependencies(app, repo, mocker, poetry):
repo.add_package(get_package("pendulum", "2.0.0"))
repo.add_package(get_package("pytest", "3.6.0"))
command = app.find("init")
command._pool = poetry.pool
mocker.patch("poetry.utils._compat.Path.open")
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__).parent
tester = CommandTester(command)
inputs = [
"my-package", # Package name
"1.2.3", # Version
"This is a description", # Description
"n", # Author
"MIT", # License
"~2.7 || ^3.6", # Python
"", # Interactive packages
"git+https://github.com/demo/demo.git", # Search for package
"", # Stop searching for packages
"", # Interactive dev packages
"pytest", # Search for package
"0",
"",
"",
"\n", # Generate
]
tester.execute(inputs="\n".join(inputs))
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
demo = {git = "https://github.com/demo/demo.git"}
[tool.poetry.dev-dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
def test_interactive_with_git_dependencies_with_reference(app, repo, mocker, poetry):
repo.add_package(get_package("pendulum", "2.0.0"))
repo.add_package(get_package("pytest", "3.6.0"))
command = app.find("init")
command._pool = poetry.pool
mocker.patch("poetry.utils._compat.Path.open")
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__).parent
tester = CommandTester(command)
inputs = [
"my-package", # Package name
"1.2.3", # Version
"This is a description", # Description
"n", # Author
"MIT", # License
"~2.7 || ^3.6", # Python
"", # Interactive packages
"git+https://github.com/demo/demo.git@develop", # Search for package
"", # Stop searching for packages
"", # Interactive dev packages
"pytest", # Search for package
"0",
"",
"",
"\n", # Generate
]
tester.execute(inputs="\n".join(inputs))
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
demo = {git = "https://github.com/demo/demo.git", rev = "develop"}
[tool.poetry.dev-dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
def test_interactive_with_git_dependencies_and_other_name(app, repo, mocker, poetry):
repo.add_package(get_package("pendulum", "2.0.0"))
repo.add_package(get_package("pytest", "3.6.0"))
command = app.find("init")
command._pool = poetry.pool
mocker.patch("poetry.utils._compat.Path.open")
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__).parent
tester = CommandTester(command)
inputs = [
"my-package", # Package name
"1.2.3", # Version
"This is a description", # Description
"n", # Author
"MIT", # License
"~2.7 || ^3.6", # Python
"", # Interactive packages
"git+https://github.com/demo/pyproject-demo.git", # Search for package
"", # Stop searching for packages
"", # Interactive dev packages
"pytest", # Search for package
"0",
"",
"",
"\n", # Generate
]
tester.execute(inputs="\n".join(inputs))
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
demo = {git = "https://github.com/demo/pyproject-demo.git"}
[tool.poetry.dev-dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
def test_interactive_with_directory_dependency(app, repo, mocker, poetry):
repo.add_package(get_package("pendulum", "2.0.0"))
repo.add_package(get_package("pytest", "3.6.0"))
command = app.find("init")
command._pool = poetry.pool
mocker.patch("poetry.utils._compat.Path.open")
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__).parent
tester = CommandTester(command)
inputs = [
"my-package", # Package name
"1.2.3", # Version
"This is a description", # Description
"n", # Author
"MIT", # License
"~2.7 || ^3.6", # Python
"", # Interactive packages
"../../fixtures/git/github.com/demo/demo", # Search for package
"", # Stop searching for packages
"", # Interactive dev packages
"pytest", # Search for package
"0",
"",
"",
"\n", # Generate
]
tester.execute(inputs="\n".join(inputs))
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
demo = {path = "../../fixtures/git/github.com/demo/demo"}
[tool.poetry.dev-dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
def test_interactive_with_directory_dependency_and_other_name(
app, repo, mocker, poetry
):
repo.add_package(get_package("pendulum", "2.0.0"))
repo.add_package(get_package("pytest", "3.6.0"))
command = app.find("init")
command._pool = poetry.pool
mocker.patch("poetry.utils._compat.Path.open")
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__).parent
tester = CommandTester(command)
inputs = [
"my-package", # Package name
"1.2.3", # Version
"This is a description", # Description
"n", # Author
"MIT", # License
"~2.7 || ^3.6", # Python
"", # Interactive packages
"../../fixtures/git/github.com/demo/pyproject-demo", # Search for package
"", # Stop searching for packages
"", # Interactive dev packages
"pytest", # Search for package
"0",
"",
"",
"\n", # Generate
]
tester.execute(inputs="\n".join(inputs))
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
demo = {path = "../../fixtures/git/github.com/demo/pyproject-demo"}
[tool.poetry.dev-dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
def test_interactive_with_file_dependency(app, repo, mocker, poetry):
repo.add_package(get_package("pendulum", "2.0.0"))
repo.add_package(get_package("pytest", "3.6.0"))
command = app.find("init")
command._pool = poetry.pool
mocker.patch("poetry.utils._compat.Path.open")
p = mocker.patch("poetry.utils._compat.Path.cwd")
p.return_value = Path(__file__).parent
tester = CommandTester(command)
inputs = [
"my-package", # Package name
"1.2.3", # Version
"This is a description", # Description
"n", # Author
"MIT", # License
"~2.7 || ^3.6", # Python
"", # Interactive packages
"../../fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl", # Search for package
"", # Stop searching for packages
"", # Interactive dev packages
"pytest", # Search for package
"0",
"",
"",
"\n", # Generate
]
tester.execute(inputs="\n".join(inputs))
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
demo = {path = "../../fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"}
[tool.poetry.dev-dependencies]
pytest = "^3.6.0"
"""
assert expected in tester.io.fetch_output()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment