Skip to content

Commit 993eaac

Browse files
committed
Add the ability to add complete dependencies in one go
1 parent abfc3f3 commit 993eaac

File tree

3 files changed

+159
-9
lines changed

3 files changed

+159
-9
lines changed

docs/docs/cli.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ You also can specify a constraint when adding a package, like so:
186186

187187
```bash
188188
poetry add pendulum@^2.0.5
189+
poetry add "pendulum>=2.0.5"
189190
```
190191

191192
If you try to add a package that is already present, you will get an error.
@@ -224,11 +225,20 @@ It means that changes in the local directory will be reflected directly in envir
224225

225226
If you don't want the dependency to be installed in editable mode you can specify it in the `pyproject.toml` file:
226227

227-
```
228+
```toml
228229
[tool.poetry.dependencies]
229230
my-package = {path = "../my/path", develop = false}
230231
```
231232

233+
If the package(s) you want to install provide extras, you can specify them
234+
when adding the package:
235+
236+
```bash
237+
poetry add requests[security,socks]
238+
poetry add "requests[security,socks]~=2.22.0"
239+
poetry add "git+https://github.com/pallets/[email protected][dotenv,dev]"
240+
```
241+
232242
### Options
233243

234244
* `--dev (-D)`: Add package as development dependency.

poetry/console/commands/init.py

+45-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
33

4+
import os
45
import re
56

67
from typing import Dict
@@ -10,7 +11,6 @@
1011

1112
from cleo import option
1213
from tomlkit import inline_table
13-
from tomlkit import table
1414

1515
from poetry.utils._compat import Path
1616
from poetry.utils._compat import OrderedDict
@@ -336,6 +336,13 @@ def _parse_requirements(
336336
cwd = Path.cwd()
337337

338338
for requirement in requirements:
339+
requirement = requirement.strip()
340+
extras = []
341+
extras_m = re.search(r"\[([\w\d,-_]+)\]$", requirement)
342+
if extras_m:
343+
extras = [e.strip() for e in extras_m.group(1).split(",")]
344+
requirement, _ = requirement.split("[")
345+
339346
if requirement.startswith(("git+https://", "git+ssh://")):
340347
url = requirement.lstrip("git+")
341348
rev = None
@@ -348,14 +355,19 @@ def _parse_requirements(
348355
if rev:
349356
pair["rev"] = rev
350357

358+
if extras:
359+
pair["extras"] = extras
360+
351361
package = Provider.get_package_from_vcs(
352362
"git", url, reference=pair.get("rev")
353363
)
354364
pair["name"] = package.name
355365
result.append(pair)
356366

357367
continue
358-
elif cwd.joinpath(requirement).exists():
368+
elif (os.path.sep in requirement or "/" in requirement) and cwd.joinpath(
369+
requirement
370+
).exists():
359371
path = cwd.joinpath(requirement)
360372
if path.is_file():
361373
package = Provider.get_package_from_file(path.resolve())
@@ -368,19 +380,47 @@ def _parse_requirements(
368380
("name", package.name),
369381
("path", path.relative_to(cwd).as_posix()),
370382
]
383+
+ ([("extras", extras)] if extras else [])
371384
)
372385
)
373386

374387
continue
375388

376-
pair = re.sub("^([^=: ]+)[@=: ](.*)$", "\\1 \\2", requirement.strip())
389+
pair = re.sub(
390+
"^([^@=: ]+)(?:@|==|(?<![<>~!])=|:| )(.*)$", "\\1 \\2", requirement
391+
)
377392
pair = pair.strip()
378393

394+
require = OrderedDict()
379395
if " " in pair:
380396
name, version = pair.split(" ", 2)
381-
result.append({"name": name, "version": version})
397+
require["name"] = name
398+
require["version"] = version
382399
else:
383-
result.append({"name": pair})
400+
m = re.match(
401+
"^([^><=!: ]+)((?:>=|<=|>|<|!=|~=|~|\^).*)$", requirement.strip()
402+
)
403+
if m:
404+
name, constraint = m.group(1), m.group(2)
405+
extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
406+
if extras_m:
407+
extras = [e.strip() for e in extras_m.group(1).split(",")]
408+
name, _ = name.split("[")
409+
410+
require["name"] = name
411+
require["version"] = constraint
412+
else:
413+
extras_m = re.search(r"\[([\w\d,-_]+)\]$", pair)
414+
if extras_m:
415+
extras = [e.strip() for e in extras_m.group(1).split(",")]
416+
pair, _ = pair.split("[")
417+
418+
require["name"] = pair
419+
420+
if extras:
421+
require["extras"] = extras
422+
423+
result.append(require)
384424

385425
return result
386426

tests/console/commands/test_add.py

+103-3
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ def test_add_no_constraint(app, repo, installer):
4242
assert content["dependencies"]["cachy"] == "^0.2.0"
4343

4444

45-
def test_add_constraint(app, repo, installer):
45+
def test_add_equal_constraint(app, repo, installer):
4646
command = app.find("add")
4747
tester = CommandTester(command)
4848

4949
repo.add_package(get_package("cachy", "0.1.0"))
5050
repo.add_package(get_package("cachy", "0.2.0"))
5151

52-
tester.execute("cachy=0.1.0")
52+
tester.execute("cachy==0.1.0")
5353

5454
expected = """\
5555
@@ -69,6 +69,67 @@ def test_add_constraint(app, repo, installer):
6969
assert len(installer.installs) == 1
7070

7171

72+
def test_add_greater_constraint(app, repo, installer):
73+
command = app.find("add")
74+
tester = CommandTester(command)
75+
76+
repo.add_package(get_package("cachy", "0.1.0"))
77+
repo.add_package(get_package("cachy", "0.2.0"))
78+
79+
tester.execute("cachy>=0.1.0")
80+
81+
expected = """\
82+
83+
Updating dependencies
84+
Resolving dependencies...
85+
86+
Writing lock file
87+
88+
89+
Package operations: 1 install, 0 updates, 0 removals
90+
91+
- Installing cachy (0.2.0)
92+
"""
93+
94+
assert expected == tester.io.fetch_output()
95+
96+
assert len(installer.installs) == 1
97+
98+
99+
def test_add_constraint_with_extras(app, repo, installer):
100+
command = app.find("add")
101+
tester = CommandTester(command)
102+
103+
cachy1 = get_package("cachy", "0.1.0")
104+
cachy1.extras = {"msgpack": [get_dependency("msgpack-python")]}
105+
msgpack_dep = get_dependency("msgpack-python", ">=0.5 <0.6", optional=True)
106+
cachy1.requires = [msgpack_dep]
107+
108+
repo.add_package(get_package("cachy", "0.2.0"))
109+
repo.add_package(cachy1)
110+
repo.add_package(get_package("msgpack-python", "0.5.3"))
111+
112+
tester.execute("cachy[msgpack]^0.1.0")
113+
114+
expected = """\
115+
116+
Updating dependencies
117+
Resolving dependencies...
118+
119+
Writing lock file
120+
121+
122+
Package operations: 2 installs, 0 updates, 0 removals
123+
124+
- Installing msgpack-python (0.5.3)
125+
- Installing cachy (0.1.0)
126+
"""
127+
128+
assert expected == tester.io.fetch_output()
129+
130+
assert len(installer.installs) == 2
131+
132+
72133
def test_add_constraint_dependencies(app, repo, installer):
73134
command = app.find("add")
74135
tester = CommandTester(command)
@@ -164,6 +225,45 @@ def test_add_git_constraint_with_poetry(app, repo, installer):
164225
assert len(installer.installs) == 2
165226

166227

228+
def test_add_git_constraint_with_extras(app, repo, installer):
229+
command = app.find("add")
230+
tester = CommandTester(command)
231+
232+
repo.add_package(get_package("pendulum", "1.4.4"))
233+
repo.add_package(get_package("cleo", "0.6.5"))
234+
repo.add_package(get_package("tomlkit", "0.5.5"))
235+
236+
tester.execute("git+https://github.com/demo/demo.git[foo,bar]")
237+
238+
expected = """\
239+
240+
Updating dependencies
241+
Resolving dependencies...
242+
243+
Writing lock file
244+
245+
246+
Package operations: 4 installs, 0 updates, 0 removals
247+
248+
- Installing cleo (0.6.5)
249+
- Installing pendulum (1.4.4)
250+
- Installing tomlkit (0.5.5)
251+
- Installing demo (0.1.2 9cf87a2)
252+
"""
253+
254+
assert expected == tester.io.fetch_output()
255+
256+
assert len(installer.installs) == 4
257+
258+
content = app.poetry.file.read()["tool"]["poetry"]
259+
260+
assert "demo" in content["dependencies"]
261+
assert content["dependencies"]["demo"] == {
262+
"git": "https://github.com/demo/demo.git",
263+
"extras": ["foo", "bar"],
264+
}
265+
266+
167267
def test_add_directory_constraint(app, repo, installer, mocker):
168268
p = mocker.patch("poetry.utils._compat.Path.cwd")
169269
p.return_value = Path(__file__) / ".."
@@ -304,7 +404,7 @@ def test_add_file_constraint_sdist(app, repo, installer, mocker):
304404
}
305405

306406

307-
def test_add_constraint_with_extras(app, repo, installer):
407+
def test_add_constraint_with_extras_option(app, repo, installer):
308408
command = app.find("add")
309409
tester = CommandTester(command)
310410

0 commit comments

Comments
 (0)