Repository: python-poetry/poetry

Issue:
ipython can't be installed with python 2.7, even when an exact version is specified
<!--
  Hi there! Thank you for discovering and submitting an issue.

  Before you submit this; let's make sure of a few things.
  Please make sure the following boxes are ticked if they are correct.
  If not, please try and fulfill these first.
-->


<!-- Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).

<!--
  Once those are done, if you're able to fill in the following list with your information,
  it'd be very helpful to whoever handles the issue.
-->
Similar to #151 , but not sure how to resolve it at all.
The command "poetry add -vvv ipython" fails with a virtualenv on python 2.7 .

- **OS version and name**: Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt9-2 (2015-04-13) x86_64 GNU/Linux
- **Poetry version**: 0.11.4
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: https://gist.github.com/buriy/8db57876a178bb2a34095c1d9ca8d388


## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
$ poetry update -vvv

[SolverProblemError]                                     
The current supported Python versions are 2.7.9                       
Because no versions of ipython match !=6.5.0                          
 and ipython (6.5.0) requires Python >=3.3, ipython is forbidden.     
So, because myproject depends on ipython (*), version solving failed.  
                                                                        
P.S.
Somehow "poetry update" works with exact version specified:
ipython = "5.8.0" but "poetry install" doesn't get it.


Discussion:
I did `poetry add iPython=5.*` which worked fine - though it'd be nice if the version could walk back to get something with compatible python when not specified
Yes, problem is not only with that last ipython for *the user python version* isn't detected correctly, but also that no user should get any cryptic or humiliating messages at all.
Right now the dialog looks like this:
 -- install ipython
 -- no, i won't, you're doing it all wrong.
I agree that there is currently a bug preventing the resolver to backtrack to the proper `ipython` version.

However, fixing it will likely break some current projects.

Let's take an example. If you have set your `python` dependency to `*` then, currently, Poetry will accept `ipython==6.5.0` since the resolver checks if the root package `python` dependency accepts **any** Python version specified by `ipython`.

This is obviously wrong and it should be the other way around: check if the ipython's Python constraint matches all Python versions specified by the `python` dependency of the root package.

However, this will break things if you have a package that declares being compatible with Python `>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*` like `pytest`, because `>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*` does not match `*` and, as such, `pytest` would be declared as incompatible.

So, there are two options here:

- Forbid the use of `*` for the `python` dependency and force users to use a proper constraint: `>=2.7` or `~2.7 || ^3.4`.
- Transparently mapping `*` for the `python` dependency to `>=2.7` since previous version are no longer supported.

I am leaning towards the second option since this is the one being the less breaking.

Resolving patch:
diff --git a/poetry/masonry/metadata.py b/poetry/masonry/metadata.py
--- a/poetry/masonry/metadata.py
+++ b/poetry/masonry/metadata.py
@@ -64,7 +64,7 @@ def from_package(cls, package):  # type: (...) -> Metadata
         meta.maintainer_email = meta.author_email
 
         # Requires python
-        if not package.python_constraint.is_any():
+        if package.python_versions != "*":
             meta.requires_python = format_python_constraint(package.python_constraint)
 
         meta.requires_dist = [d.to_pep_508() for d in package.requires]
diff --git a/poetry/packages/project_package.py b/poetry/packages/project_package.py
--- a/poetry/packages/project_package.py
+++ b/poetry/packages/project_package.py
@@ -1,3 +1,6 @@
+from poetry.semver import VersionRange
+from poetry.semver import parse_constraint
+
 from .package import Package
 
 
@@ -10,6 +13,9 @@ def __init__(self, name, version, pretty_version=None):
         self.include = []
         self.exclude = []
 
+        if self._python_versions == "*":
+            self._python_constraint = parse_constraint("~2.7 || >=3.4")
+
     def is_root(self):
         return True
 
@@ -19,3 +25,15 @@ def to_dependency(self):
         dependency.is_root = True
 
         return dependency
+
+    @property
+    def python_versions(self):
+        return self._python_versions
+
+    @python_versions.setter
+    def python_versions(self, value):
+        self._python_versions = value
+        if value == "*" or value == VersionRange():
+            value = "~2.7 || >=3.4"
+
+        self._python_constraint = parse_constraint(value)
diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py
--- a/poetry/puzzle/provider.py
+++ b/poetry/puzzle/provider.py
@@ -281,21 +281,27 @@ def incompatibilities_for(
         else:
             dependencies = package.requires
 
-        if not self._package.python_constraint.allows_any(package.python_constraint):
-            return [
-                Incompatibility(
-                    [Term(package.to_dependency(), True)],
-                    PythonCause(package.python_versions, self._package.python_versions),
-                )
-            ]
-
-        if not self._package.platform_constraint.matches(package.platform_constraint):
-            return [
-                Incompatibility(
-                    [Term(package.to_dependency(), True)],
-                    PlatformCause(package.platform),
-                )
-            ]
+            if not package.python_constraint.allows_all(
+                self._package.python_constraint
+            ):
+                return [
+                    Incompatibility(
+                        [Term(package.to_dependency(), True)],
+                        PythonCause(
+                            package.python_versions, self._package.python_versions
+                        ),
+                    )
+                ]
+
+            if not self._package.platform_constraint.matches(
+                package.platform_constraint
+            ):
+                return [
+                    Incompatibility(
+                        [Term(package.to_dependency(), True)],
+                        PlatformCause(package.platform),
+                    )
+                ]
 
         dependencies = [
             dep


Test patch:
diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py
--- a/tests/puzzle/test_solver.py
+++ b/tests/puzzle/test_solver.py
@@ -290,17 +290,17 @@ def test_solver_sets_categories(solver, repo, package):
 
 
 def test_solver_respects_root_package_python_versions(solver, repo, package):
-    package.python_versions = "^3.4"
+    package.python_versions = "~3.4"
     package.add_dependency("A")
     package.add_dependency("B")
 
     package_a = get_package("A", "1.0")
     package_b = get_package("B", "1.0")
-    package_b.python_versions = "^3.6"
+    package_b.python_versions = "^3.3"
     package_c = get_package("C", "1.0")
-    package_c.python_versions = "^3.6"
+    package_c.python_versions = "^3.4"
     package_c11 = get_package("C", "1.1")
-    package_c11.python_versions = "~3.3"
+    package_c11.python_versions = "^3.6"
     package_b.add_dependency("C", "^1.0")
 
     repo.add_package(package_a)
@@ -341,16 +341,16 @@ def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package):
 
 
 def test_solver_solves_optional_and_compatible_packages(solver, repo, package):
-    package.python_versions = "^3.4"
+    package.python_versions = "~3.4"
     package.extras["foo"] = [get_dependency("B")]
-    package.add_dependency("A", {"version": "*", "python": "~3.5"})
+    package.add_dependency("A", {"version": "*", "python": "^3.4"})
     package.add_dependency("B", {"version": "*", "optional": True})
 
     package_a = get_package("A", "1.0")
     package_b = get_package("B", "1.0")
-    package_b.python_versions = "^3.6"
+    package_b.python_versions = "^3.3"
     package_c = get_package("C", "1.0")
-    package_c.python_versions = "^3.6"
+    package_c.python_versions = "^3.4"
     package_b.add_dependency("C", "^1.0")
 
     repo.add_package(package_a)
@@ -376,7 +376,6 @@ def test_solver_solves_while_respecting_root_platforms(solver, repo, package):
 
     package_a = get_package("A", "1.0")
     package_b = get_package("B", "1.0")
-    package_b.python_versions = "^3.6"
     package_c12 = get_package("C", "1.2")
     package_c12.platform = "win32"
     package_c10 = get_package("C", "1.0")