Skip to content

Commit

Permalink
Add the ability to add complete dependencies in one go
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Jul 13, 2019
1 parent abfc3f3 commit 993eaac
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 9 deletions.
12 changes: 11 additions & 1 deletion docs/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ 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.
Expand Down Expand Up @@ -224,11 +225,20 @@ 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.
Expand Down
50 changes: 45 additions & 5 deletions poetry/console/commands/init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import os
import re

from typing import Dict
Expand All @@ -10,7 +11,6 @@

from cleo import option
from tomlkit import inline_table
from tomlkit import table

from poetry.utils._compat import Path
from poetry.utils._compat import OrderedDict
Expand Down Expand Up @@ -336,6 +336,13 @@ def _parse_requirements(
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
Expand All @@ -348,14 +355,19 @@ def _parse_requirements(
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 cwd.joinpath(requirement).exists():
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())
Expand All @@ -368,19 +380,47 @@ def _parse_requirements(
("name", package.name),
("path", path.relative_to(cwd).as_posix()),
]
+ ([("extras", extras)] if extras else [])
)
)

continue

pair = re.sub("^([^=: ]+)[@=: ](.*)$", "\\1 \\2", requirement.strip())
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

Expand Down
106 changes: 103 additions & 3 deletions tests/console/commands/test_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ def test_add_no_constraint(app, repo, installer):
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 = """\
Expand All @@ -69,6 +69,67 @@ def test_add_constraint(app, repo, installer):
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)
Expand Down Expand Up @@ -164,6 +225,45 @@ def test_add_git_constraint_with_poetry(app, repo, installer):
assert len(installer.installs) == 2


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__) / ".."
Expand Down Expand Up @@ -304,7 +404,7 @@ def test_add_file_constraint_sdist(app, repo, installer, mocker):
}


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)

Expand Down

0 comments on commit 993eaac

Please sign in to comment.