Repository: python-poetry/poetry

Issue:
Allow to specify the source when adding a package
- [x] I have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] I have searched the [documentation](https://python-poetry.org/docs/) and believe that my question is not covered.

## Feature Request
Since Poetry 1.0 we can specify the source of package in the `pyproject.toml`, in order to use another index than PyPI. This is very convenient, but there is no way to specify this when using the `poetry add` command.

This is especially inconvenient when you're trying to update an existing private dependency with `poetry add`, as it will erase its source.

My suggestion would be to add a `--source` option to `poetry add`, taking the name of a source defined in the `pyproject.toml`. I don't think adding a package from a source not defined previously should be accepted. 


Discussion:
I'm working on a PR, the solution seems to be very easy in the end.

Resolving patch:
diff --git a/poetry/console/commands/add.py b/poetry/console/commands/add.py
--- a/poetry/console/commands/add.py
+++ b/poetry/console/commands/add.py
@@ -33,6 +33,12 @@ class AddCommand(EnvCommand, InitCommand):
             "Platforms for which the dependency must be installed.",
             flag=False,
         ),
+        option(
+            "source",
+            None,
+            "Name of the source to use to install the package.",
+            flag=False,
+        ),
         option("allow-prereleases", None, "Accept prereleases."),
         option(
             "dry-run",
@@ -86,7 +92,9 @@ def handle(self):
                     raise ValueError("Package {} is already present".format(name))
 
         requirements = self._determine_requirements(
-            packages, allow_prereleases=self.option("allow-prereleases")
+            packages,
+            allow_prereleases=self.option("allow-prereleases"),
+            source=self.option("source"),
         )
 
         for _constraint in requirements:
@@ -123,6 +131,9 @@ def handle(self):
             if self.option("platform"):
                 constraint["platform"] = self.option("platform")
 
+            if self.option("source"):
+                constraint["source"] = self.option("source")
+
             if len(constraint) == 1 and "version" in constraint:
                 constraint = constraint["version"]
 
diff --git a/poetry/console/commands/init.py b/poetry/console/commands/init.py
--- a/poetry/console/commands/init.py
+++ b/poetry/console/commands/init.py
@@ -204,7 +204,7 @@ def handle(self):
             f.write(content)
 
     def _determine_requirements(
-        self, requires, allow_prereleases=False
+        self, requires, allow_prereleases=False, source=None
     ):  # type: (List[str], bool) -> List[Dict[str, str]]
         if not requires:
             requires = []
@@ -300,7 +300,9 @@ def _determine_requirements(
             elif "version" not in requirement:
                 # determine the best version automatically
                 name, version = self._find_best_version_for_package(
-                    requirement["name"], allow_prereleases=allow_prereleases
+                    requirement["name"],
+                    allow_prereleases=allow_prereleases,
+                    source=source,
                 )
                 requirement["version"] = version
                 requirement["name"] = name
@@ -315,6 +317,7 @@ def _determine_requirements(
                     requirement["name"],
                     requirement["version"],
                     allow_prereleases=allow_prereleases,
+                    source=source,
                 )
 
                 requirement["name"] = name
@@ -324,13 +327,13 @@ def _determine_requirements(
         return result
 
     def _find_best_version_for_package(
-        self, name, required_version=None, allow_prereleases=False
+        self, name, required_version=None, allow_prereleases=False, source=None
     ):  # type: (...) -> Tuple[str, str]
         from poetry.version.version_selector import VersionSelector
 
         selector = VersionSelector(self._get_pool())
         package = selector.find_best_candidate(
-            name, required_version, allow_prereleases=allow_prereleases
+            name, required_version, allow_prereleases=allow_prereleases, source=source
         )
 
         if not package:
diff --git a/poetry/version/version_selector.py b/poetry/version/version_selector.py
--- a/poetry/version/version_selector.py
+++ b/poetry/version/version_selector.py
@@ -15,6 +15,7 @@ def find_best_candidate(
         package_name,  # type: str
         target_package_version=None,  # type:  Union[str, None]
         allow_prereleases=False,  # type: bool
+        source=None,  # type: str
     ):  # type: (...) -> Union[Package, bool]
         """
         Given a package name and optional version,
@@ -26,7 +27,7 @@ def find_best_candidate(
             constraint = parse_constraint("*")
 
         candidates = self._pool.find_packages(
-            package_name, constraint, allow_prereleases=True
+            package_name, constraint, allow_prereleases=True, repository=source
         )
         only_prereleases = all([c.version.is_prerelease() for c in candidates])
 


Test patch:
diff --git a/tests/console/commands/test_add.py b/tests/console/commands/test_add.py
--- a/tests/console/commands/test_add.py
+++ b/tests/console/commands/test_add.py
@@ -4,6 +4,8 @@
 
 from cleo.testers import CommandTester
 
+from poetry.repositories.legacy_repository import LegacyRepository
+from poetry.semver import Version
 from poetry.utils._compat import Path
 from tests.helpers import get_dependency
 from tests.helpers import get_package
@@ -634,6 +636,72 @@ def test_add_constraint_with_platform(app, repo, installer):
     }
 
 
+def test_add_constraint_with_source(app, poetry, installer):
+    repo = LegacyRepository(name="my-index", url="https://my-index.fake")
+    repo.add_package(get_package("cachy", "0.2.0"))
+    repo._cache.store("matches").put("cachy:0.2.0", [Version.parse("0.2.0")], 5)
+
+    poetry.pool.add_repository(repo)
+
+    command = app.find("add")
+    tester = CommandTester(command)
+
+    tester.execute("cachy=0.2.0 --source my-index")
+
+    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
+
+    content = app.poetry.file.read()["tool"]["poetry"]
+
+    assert "cachy" in content["dependencies"]
+    assert content["dependencies"]["cachy"] == {
+        "version": "0.2.0",
+        "source": "my-index",
+    }
+
+
+def test_add_constraint_with_source_that_does_not_exist(app):
+    command = app.find("add")
+    tester = CommandTester(command)
+
+    with pytest.raises(ValueError) as e:
+        tester.execute("foo --source i-dont-exist")
+
+    assert 'Repository "i-dont-exist" does not exist.' == str(e.value)
+
+
+def test_add_constraint_not_found_with_source(app, poetry, mocker):
+    repo = LegacyRepository(name="my-index", url="https://my-index.fake")
+    mocker.patch.object(repo, "find_packages", return_value=[])
+
+    poetry.pool.add_repository(repo)
+
+    pypi = poetry.pool.repositories[0]
+    pypi.add_package(get_package("cachy", "0.2.0"))
+
+    command = app.find("add")
+    tester = CommandTester(command)
+
+    with pytest.raises(ValueError) as e:
+        tester.execute("cachy --source my-index")
+
+    assert "Could not find a matching version of package cachy" == str(e.value)
+
+
 def test_add_to_section_that_does_no_exist_yet(app, repo, installer):
     command = app.find("add")
     tester = CommandTester(command)